Skip to content

Commit bdf6179

Browse files
fix: (android) fix issue with minimumDate/maximumDate when using timeZoneOffsetInMinutes (#519)
* Fixes #518 * Add setTimeZone * Revert setting end of day * Add toggleMinMaxDate * Better Demo * Add E2E test for min/max * Fix Android test * Update android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java Co-authored-by: Vojtech Novak <vonovak@gmail.com> * Update example/App.js Co-authored-by: Vojtech Novak <vonovak@gmail.com> * More consistency * Fix ESLint error Co-authored-by: Vojtech Novak <vonovak@gmail.com>
1 parent c9459c9 commit bdf6179

File tree

3 files changed

+103
-4
lines changed

3 files changed

+103
-4
lines changed

android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerDialogFragment.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.util.Calendar;
2727
import java.util.Locale;
28+
import java.util.TimeZone;
2829

2930
@SuppressLint("ValidFragment")
3031
public class RNDatePickerDialogFragment extends DialogFragment {
@@ -108,6 +109,11 @@ static DatePickerDialog createDialog(
108109

109110
final DatePicker datePicker = dialog.getDatePicker();
110111

112+
Integer timeZoneOffsetInMilliseconds = getTimeZoneOffset(args);
113+
if (timeZoneOffsetInMilliseconds != null) {
114+
c.setTimeZone(TimeZone.getTimeZone("GMT"));
115+
}
116+
111117
if (args != null && args.containsKey(RNConstants.ARG_MINDATE)) {
112118
// Set minDate to the beginning of the day. We need this because of clowniness in datepicker
113119
// that causes it to throw an exception if minDate is greater than the internal timestamp
@@ -117,7 +123,7 @@ static DatePickerDialog createDialog(
117123
c.set(Calendar.MINUTE, 0);
118124
c.set(Calendar.SECOND, 0);
119125
c.set(Calendar.MILLISECOND, 0);
120-
datePicker.setMinDate(c.getTimeInMillis());
126+
datePicker.setMinDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds));
121127
} else {
122128
// This is to work around a bug in DatePickerDialog where it doesn't display a title showing
123129
// the date under certain conditions.
@@ -130,12 +136,29 @@ static DatePickerDialog createDialog(
130136
c.set(Calendar.MINUTE, 59);
131137
c.set(Calendar.SECOND, 59);
132138
c.set(Calendar.MILLISECOND, 999);
133-
datePicker.setMaxDate(c.getTimeInMillis());
139+
datePicker.setMaxDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds));
134140
}
135141

136142
return dialog;
137143
}
138144

145+
private static Integer getTimeZoneOffset(Bundle args) {
146+
if (args != null && args.containsKey(RNConstants.ARG_TZOFFSET_MINS)) {
147+
long timeZoneOffsetInMinutesFallback = args.getLong(RNConstants.ARG_TZOFFSET_MINS);
148+
int timeZoneOffsetInMinutes = args.getInt(RNConstants.ARG_TZOFFSET_MINS, (int) timeZoneOffsetInMinutesFallback);
149+
return timeZoneOffsetInMinutes * 60000;
150+
}
151+
152+
return null;
153+
}
154+
155+
private static int getOffset(Calendar c, Integer timeZoneOffsetInMilliseconds) {
156+
if (timeZoneOffsetInMilliseconds != null) {
157+
return TimeZone.getDefault().getOffset(c.getTimeInMillis()) - timeZoneOffsetInMilliseconds;
158+
}
159+
return 0;
160+
}
161+
139162
@Override
140163
public void onDismiss(DialogInterface dialog) {
141164
super.onDismiss(dialog);

example/App.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ const MINUTE_INTERVALS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30];
6262

6363
export const App = () => {
6464
// Sat, 13 Nov 2021 10:00:00 GMT (local: Saturday, November 13, 2021 11:00:00 AM GMT+01:00)
65-
const sourceDate = moment.unix(1636797600).local().toDate();
65+
const sourceMoment = moment.unix(1636797600);
66+
const sourceDate = sourceMoment.local().toDate();
6667
const [date, setDate] = useState(sourceDate);
6768
const [tzOffsetInMinutes, setTzOffsetInMinutes] = useState(undefined);
6869
const [mode, setMode] = useState(MODE_VALUES[0]);
@@ -72,6 +73,8 @@ export const App = () => {
7273
const [interval, setMinInterval] = useState(1);
7374
const [neutralButtonLabel, setNeutralButtonLabel] = useState(undefined);
7475
const [disabled, setDisabled] = useState(false);
76+
const [minimumDate, setMinimumDate] = useState();
77+
const [maximumDate, setMaximumDate] = useState();
7578

7679
// Windows-specific
7780
const [time, setTime] = useState(undefined);
@@ -111,6 +114,17 @@ export const App = () => {
111114
backgroundColor: isDarkMode ? Colors.dark : Colors.lighter,
112115
};
113116

117+
const toggleMinMaxDate = () => {
118+
const startOfTodayUTC = sourceMoment.utc().startOf('day').toDate();
119+
setMinimumDate(maximumDate ? undefined : startOfTodayUTC);
120+
const endOfTomorrowUTC = sourceMoment
121+
.utc()
122+
.endOf('day')
123+
.add(1, 'day')
124+
.toDate();
125+
setMaximumDate(minimumDate ? undefined : endOfTomorrowUTC);
126+
};
127+
114128
if (Platform.OS !== 'windows') {
115129
return (
116130
<SafeAreaView style={[backgroundStyle, {flex: 1}]}>
@@ -267,11 +281,23 @@ export const App = () => {
267281
title="setTzOffsetInMinutes to 120"
268282
/>
269283
</View>
284+
<View style={styles.button}>
285+
<Button
286+
testID="setMinMax"
287+
onPress={() => {
288+
toggleMinMaxDate();
289+
setShow(true);
290+
}}
291+
title="toggleMinMaxDate"
292+
/>
293+
</View>
270294
{show && (
271295
<DateTimePicker
272296
testID="dateTimePicker"
273297
timeZoneOffsetInMinutes={tzOffsetInMinutes}
274298
minuteInterval={interval}
299+
maximumDate={maximumDate}
300+
minimumDate={minimumDate}
275301
value={date}
276302
mode={mode}
277303
is24Hour

example/e2e/detoxTest.spec.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ describe('Example', () => {
168168
});
169169

170170
it('setTz should change time text when setTzOffsetInMinutes is 120 minutes', async () => {
171-
await element(by.id('DateTimePickerScrollView')).scrollTo('bottom');
171+
await elementById('DateTimePickerScrollView').scrollTo('bottom');
172172
await userOpensPicker({
173173
mode: 'time',
174174
display: getPickerDisplay(),
@@ -186,6 +186,56 @@ describe('Example', () => {
186186
}
187187
await expect(getTimeText()).toHaveText('09:30');
188188
});
189+
190+
it('should let you pick tomorrow but not yesterday when setting min/max', async () => {
191+
await elementById('DateTimePickerScrollView').scrollTo('bottom');
192+
await elementById('setTzOffsetToZero').tap();
193+
await elementById('setMinMax').tap();
194+
195+
if (isIOS()) {
196+
const testElement = getDateTimePickerControlIOS();
197+
198+
// Ensure you can't select yesterday (iOS)
199+
await testElement.setDatePickerDate('2021-11-12', 'yyyy-MM-dd');
200+
await expect(getDateText()).toHaveText('11/13/2021');
201+
202+
// Ensure you can select tomorrow (iOS)
203+
await userOpensPicker({mode: 'date', display: getPickerDisplay()});
204+
await testElement.setDatePickerDate('2021-11-14', 'yyyy-MM-dd');
205+
} else {
206+
const uiDevice = device.getUiDevice();
207+
208+
// Ensure you can't select yesterday (Android)
209+
const focusTwelethOfNovemberInCalendar = async () => {
210+
for (var i = 0; i < 4; i++) {
211+
await uiDevice.pressDPadDown();
212+
}
213+
for (var i = 0; i < 3; i++) {
214+
await uiDevice.pressDPadLeft();
215+
}
216+
};
217+
await focusTwelethOfNovemberInCalendar();
218+
await uiDevice.pressEnter();
219+
await userTapsOkButtonAndroid();
220+
await expect(getDateText()).toHaveText('11/13/2021');
221+
222+
// Ensure you can select tomorrow (Android)
223+
await userOpensPicker({mode: 'date', display: getPickerDisplay()});
224+
const focusFourteenthOfNovemberInCalendar = async () => {
225+
for (var i = 0; i < 5; i++) {
226+
await uiDevice.pressDPadDown();
227+
}
228+
for (var i = 0; i < 2; i++) {
229+
await uiDevice.pressDPadLeft();
230+
}
231+
};
232+
await focusFourteenthOfNovemberInCalendar();
233+
await uiDevice.pressEnter();
234+
await userTapsOkButtonAndroid();
235+
}
236+
237+
await expect(getDateText()).toHaveText('11/14/2021');
238+
});
189239
});
190240

191241
it(':android: given we specify neutralButtonLabel, tapping the corresponding button sets date to the beginning of the unix time epoch', async () => {

0 commit comments

Comments
 (0)