Skip to content

Commit

Permalink
fix: (android) fix issue with minimumDate/maximumDate when using time…
Browse files Browse the repository at this point in the history
…ZoneOffsetInMinutes (#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 <[email protected]>

* Update example/App.js

Co-authored-by: Vojtech Novak <[email protected]>

* More consistency

* Fix ESLint error

Co-authored-by: Vojtech Novak <[email protected]>
  • Loading branch information
PaitoAnderson and vonovak authored Dec 19, 2021
1 parent c9459c9 commit bdf6179
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;

@SuppressLint("ValidFragment")
public class RNDatePickerDialogFragment extends DialogFragment {
Expand Down Expand Up @@ -108,6 +109,11 @@ static DatePickerDialog createDialog(

final DatePicker datePicker = dialog.getDatePicker();

Integer timeZoneOffsetInMilliseconds = getTimeZoneOffset(args);
if (timeZoneOffsetInMilliseconds != null) {
c.setTimeZone(TimeZone.getTimeZone("GMT"));
}

if (args != null && args.containsKey(RNConstants.ARG_MINDATE)) {
// Set minDate to the beginning of the day. We need this because of clowniness in datepicker
// that causes it to throw an exception if minDate is greater than the internal timestamp
Expand All @@ -117,7 +123,7 @@ static DatePickerDialog createDialog(
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
datePicker.setMinDate(c.getTimeInMillis());
datePicker.setMinDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds));
} else {
// This is to work around a bug in DatePickerDialog where it doesn't display a title showing
// the date under certain conditions.
Expand All @@ -130,12 +136,29 @@ static DatePickerDialog createDialog(
c.set(Calendar.MINUTE, 59);
c.set(Calendar.SECOND, 59);
c.set(Calendar.MILLISECOND, 999);
datePicker.setMaxDate(c.getTimeInMillis());
datePicker.setMaxDate(c.getTimeInMillis() - getOffset(c, timeZoneOffsetInMilliseconds));
}

return dialog;
}

private static Integer getTimeZoneOffset(Bundle args) {
if (args != null && args.containsKey(RNConstants.ARG_TZOFFSET_MINS)) {
long timeZoneOffsetInMinutesFallback = args.getLong(RNConstants.ARG_TZOFFSET_MINS);
int timeZoneOffsetInMinutes = args.getInt(RNConstants.ARG_TZOFFSET_MINS, (int) timeZoneOffsetInMinutesFallback);
return timeZoneOffsetInMinutes * 60000;
}

return null;
}

private static int getOffset(Calendar c, Integer timeZoneOffsetInMilliseconds) {
if (timeZoneOffsetInMilliseconds != null) {
return TimeZone.getDefault().getOffset(c.getTimeInMillis()) - timeZoneOffsetInMilliseconds;
}
return 0;
}

@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
Expand Down
28 changes: 27 additions & 1 deletion example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ const MINUTE_INTERVALS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30];

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

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

const toggleMinMaxDate = () => {
const startOfTodayUTC = sourceMoment.utc().startOf('day').toDate();
setMinimumDate(maximumDate ? undefined : startOfTodayUTC);
const endOfTomorrowUTC = sourceMoment
.utc()
.endOf('day')
.add(1, 'day')
.toDate();
setMaximumDate(minimumDate ? undefined : endOfTomorrowUTC);
};

if (Platform.OS !== 'windows') {
return (
<SafeAreaView style={[backgroundStyle, {flex: 1}]}>
Expand Down Expand Up @@ -267,11 +281,23 @@ export const App = () => {
title="setTzOffsetInMinutes to 120"
/>
</View>
<View style={styles.button}>
<Button
testID="setMinMax"
onPress={() => {
toggleMinMaxDate();
setShow(true);
}}
title="toggleMinMaxDate"
/>
</View>
{show && (
<DateTimePicker
testID="dateTimePicker"
timeZoneOffsetInMinutes={tzOffsetInMinutes}
minuteInterval={interval}
maximumDate={maximumDate}
minimumDate={minimumDate}
value={date}
mode={mode}
is24Hour
Expand Down
52 changes: 51 additions & 1 deletion example/e2e/detoxTest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('Example', () => {
});

it('setTz should change time text when setTzOffsetInMinutes is 120 minutes', async () => {
await element(by.id('DateTimePickerScrollView')).scrollTo('bottom');
await elementById('DateTimePickerScrollView').scrollTo('bottom');
await userOpensPicker({
mode: 'time',
display: getPickerDisplay(),
Expand All @@ -186,6 +186,56 @@ describe('Example', () => {
}
await expect(getTimeText()).toHaveText('09:30');
});

it('should let you pick tomorrow but not yesterday when setting min/max', async () => {
await elementById('DateTimePickerScrollView').scrollTo('bottom');
await elementById('setTzOffsetToZero').tap();
await elementById('setMinMax').tap();

if (isIOS()) {
const testElement = getDateTimePickerControlIOS();

// Ensure you can't select yesterday (iOS)
await testElement.setDatePickerDate('2021-11-12', 'yyyy-MM-dd');
await expect(getDateText()).toHaveText('11/13/2021');

// Ensure you can select tomorrow (iOS)
await userOpensPicker({mode: 'date', display: getPickerDisplay()});
await testElement.setDatePickerDate('2021-11-14', 'yyyy-MM-dd');
} else {
const uiDevice = device.getUiDevice();

// Ensure you can't select yesterday (Android)
const focusTwelethOfNovemberInCalendar = async () => {
for (var i = 0; i < 4; i++) {
await uiDevice.pressDPadDown();
}
for (var i = 0; i < 3; i++) {
await uiDevice.pressDPadLeft();
}
};
await focusTwelethOfNovemberInCalendar();
await uiDevice.pressEnter();
await userTapsOkButtonAndroid();
await expect(getDateText()).toHaveText('11/13/2021');

// Ensure you can select tomorrow (Android)
await userOpensPicker({mode: 'date', display: getPickerDisplay()});
const focusFourteenthOfNovemberInCalendar = async () => {
for (var i = 0; i < 5; i++) {
await uiDevice.pressDPadDown();
}
for (var i = 0; i < 2; i++) {
await uiDevice.pressDPadLeft();
}
};
await focusFourteenthOfNovemberInCalendar();
await uiDevice.pressEnter();
await userTapsOkButtonAndroid();
}

await expect(getDateText()).toHaveText('11/14/2021');
});
});

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

0 comments on commit bdf6179

Please sign in to comment.