Skip to content

Commit

Permalink
Bugfix: Scroll away from invalid dates (henninghall#280)
Browse files Browse the repository at this point in the history
* initial working version

* fix native variant issue

* cleanup

* cleanup
  • Loading branch information
henninghall authored Dec 28, 2020
1 parent 5547b72 commit 26f7316
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class WheelChangeListenerImpl implements WheelChangeListener {
Expand All @@ -30,33 +29,96 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
this.rootView = rootView;
}

private SimpleDateFormat getDateFormat(){
TimeZone timeZone = state.getTimeZone();
SimpleDateFormat dateFormat = uiManager.getDateFormat();
dateFormat.setTimeZone(timeZone);
return dateFormat;
}

@Override
public void onChange(Wheel picker) {
if(wheels.hasSpinningWheel()) return;

WritableMap event = Arguments.createMap();
TimeZone timeZone = state.getTimeZone();
SimpleDateFormat dateFormat = uiManager.getDateFormat();
if(!exists()){
Calendar closestExistingDate = getClosestExistingDateInPast();
if(closestExistingDate != null) {
uiManager.animateToDate(closestExistingDate);
}
return;
}

Calendar selectedDate = getSelectedDate();
if(selectedDate == null) return;

Calendar minDate = state.getMinimumDate();
if (minDate != null && selectedDate.before(minDate)) {
uiManager.animateToDate(minDate);
return;
}

Calendar maxDate = state.getMaximumDate();
if (maxDate != null && selectedDate.after(maxDate)) {
uiManager.animateToDate(maxDate);
return;
}

emitDateChangeEvent(selectedDate);
}

// Example: Jan 1 returns true, April 31 returns false.
private boolean exists(){
SimpleDateFormat dateFormat = getDateFormat();
String toParse = wheels.getDateTimeString();
try {
dateFormat.setTimeZone(timeZone);
Calendar date = Calendar.getInstance(timeZone);
String toParse = wheels.getDateString();
Date newDate = dateFormat.parse(toParse);
date.setTime(newDate);
String dateString = Utils.dateToIso(date);
if (minDate != null && date.before(minDate)) uiManager.animateToDate(minDate);
else if (maxDate != null && date.after(maxDate)) uiManager.animateToDate(maxDate);
else {
event.putString("date", dateString);
event.putString("dateString", uiManager.getDisplayValueString());
DatePickerManager.context.getJSModule(RCTEventEmitter.class)
.receiveEvent(rootView.getId(), "dateChange", event);
}
dateFormat.setLenient(false); // disallow parsing invalid dates
dateFormat.parse(toParse);
return true;
} catch (ParseException e) {
return false;
}
}

private Calendar getSelectedDate(){
SimpleDateFormat dateFormat = getDateFormat();
String toParse = wheels.getDateTimeString();
TimeZone timeZone = state.getTimeZone();
Calendar date = Calendar.getInstance(timeZone);
try {
dateFormat.setLenient(true); // allow parsing invalid dates
date.setTime(dateFormat.parse(toParse));
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}

private Calendar getClosestExistingDateInPast(){
SimpleDateFormat dateFormat = getDateFormat();
dateFormat.setLenient(false); // disallow parsing invalid dates

int maxDaysInPastToCheck = 10;
for (int i = 0; i < maxDaysInPastToCheck; i++){
try {
String toParse = wheels.getDateTimeString(i);
Calendar calendar = Calendar.getInstance(state.getTimeZone());
calendar.setTime(dateFormat.parse(toParse));
return calendar;
} catch (ParseException ignored) {
// continue checking if exception (which means invalid date)
}
}
return null;
}

private void emitDateChangeEvent(Calendar date) {
WritableMap event = Arguments.createMap();
String dateString = Utils.dateToIso(date);
event.putString("date", dateString);
event.putString("dateString", uiManager.getDisplayValueString());
DatePickerManager.context.getJSModule(RCTEventEmitter.class)
.receiveEvent(rootView.getId(), "dateChange", event);
}

}
41 changes: 31 additions & 10 deletions android/src/main/java/com/henninghall/date_picker/ui/Wheels.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.henninghall.date_picker.pickers.Picker;
import com.henninghall.date_picker.R;
import com.henninghall.date_picker.State;
import com.henninghall.date_picker.Utils;
import com.henninghall.date_picker.models.WheelType;
import com.henninghall.date_picker.models.Mode;
import com.henninghall.date_picker.wheelFunctions.SetDividerHeight;
Expand Down Expand Up @@ -112,19 +111,41 @@ Wheel getWheel(WheelType type){
return wheelPerWheelType.get(type);
}

String getDateString() {
String getDateTimeString(int daysToSubtract) {
return getDateString(daysToSubtract) + " " + getTimeString();
}

private String getDateModeString(int daysToSubtract) {
ArrayList<Wheel> wheels = getOrderedVisibleWheels();
String dateString = (state.getMode() == Mode.date)
? wheels.get(0).getValue() + " "
+ wheels.get(1).getValue() + " "
+ wheels.get(2).getValue()
: dayWheel.getValue();
return dateString
+ " " + hourWheel.getValue()
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++) {
if (i != 0) sb.append(" ");
Wheel w = wheels.get(i);
if (w instanceof DateWheel) {
sb.append(w.getPastValue(daysToSubtract));
}
else sb.append(w.getValue());
}
return sb.toString();
}

private String getDateString(int daysToSubtract){
if(state.getMode() == Mode.date ){
return getDateModeString(daysToSubtract);
}
return dayWheel.getValue();
}

private String getTimeString(){
return hourWheel.getValue()
+ " " + minutesWheel.getValue()
+ ampmWheel.getValue();
}

String getDateTimeString() {
return getDateTimeString(0);
}

String getDisplayValue() {
StringBuilder sb = new StringBuilder();
for (Wheel wheel: getOrderedVisibleWheels()) {
Expand All @@ -137,7 +158,7 @@ private void addInOrder(){
ArrayList<WheelType> wheels = state.derived.getOrderedVisibleWheels();
for (WheelType wheelType : wheels) {
Wheel wheel = getWheel(wheelType);
pickerWrapper.addPicker(wheel.picker.getView());
pickerWrapper.addPicker(wheel.picker.getView());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public ArrayList<String> getValues() {
Calendar cal = Calendar.getInstance();
ArrayList<String> values = new ArrayList<>();
cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DATE, 0);
cal.set(Calendar.DATE, 1);
final int maxDate = 31;
final int minDate = 1;
for (int i = minDate; i <= maxDate; ++i) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public String getValue() {
return getValueAtIndex(getIndex());
}

public String getPastValue(int subtractIndex) {
if(!visible()) return format.format(userSetValue.getTime());
int size = values.size();
int pastValueIndex = (getIndex() + size - subtractIndex) % size;
return getValueAtIndex(pastValueIndex);
}


private int getIndex() {
return picker.getValue();
}
Expand Down
52 changes: 52 additions & 0 deletions examples/detox/e2e/tests/invalidDates.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { scrollWheel, expectDate, setDate, setMode, setMaximumDate, setMinimumDate } = require('../utils')

const scrollDays = (days) => scrollWheel(1, days)

describe('Invalid dates', () => {
before(async () => {
await device.reloadReactNative()
await element(by.text('Advanced')).tap()
await setMinimumDate(undefined)
await setMaximumDate(undefined)
await setMode('date')
})


it('scrolls back to last valid date', async () => {
await setDate(new Date("2001-02-28 00:00"))
await scrollDays(1)
await expectDate('2001-02-28 00:00:00')
})

it('scrolls back after scrolling multiple dates', async () => {
await setDate(new Date("2001-02-27 00:00"))
await scrollDays(2)
await expectDate('2001-02-28 00:00:00')
})

it('not scrolling back on unusual valid dates', async () => {
await setDate(new Date("2000-02-28 00:00"))
await scrollDays(1)
await expectDate('2000-02-29 00:00:00')
})

it('not scrolling back after scrolling past invalid dates', async () => {
await setDate(new Date("2001-02-28 00:00"))
await scrollDays(4)
await expectDate('2001-02-01 00:00:00')
})

it('works on months with 30 days', async () => {
await setDate(new Date("2001-04-30 00:00"))
await scrollDays(1)
await expectDate('2001-04-30 00:00:00')
})

it('works on months with 31 days', async () => {
await setDate(new Date("2001-05-30 00:00"))
await scrollDays(1)
await expectDate('2001-05-31 00:00:00')
})


})

0 comments on commit 26f7316

Please sign in to comment.