From 4eaae734b52ecfbc75b5f66ba52e1c61a93a1984 Mon Sep 17 00:00:00 2001 From: GeoffreySautieres Date: Mon, 13 Oct 2025 21:48:49 +0200 Subject: [PATCH 1/3] feat(DatePicker): add possibility to select mode yearAndMonth --- README.md | 27 +++++++++++++---- .../henninghall/date_picker/DerivedData.java | 5 ++++ .../henninghall/date_picker/models/Mode.java | 2 +- .../henninghall/date_picker/ui/Wheels.java | 18 ++++++++++++ .../date_picker/wheels/HourWheel.java | 2 +- .../date_picker/wheels/MinutesWheel.java | 2 +- .../date_picker/wheels/MonthWheel.java | 2 +- .../date_picker/wheels/YearWheel.java | 2 +- index.d.ts | 2 +- ios/RNDatePicker.mm | 7 +++++ ios/RNDatePickerManager.mm | 29 +++++++++++++++---- npmREADME.md | 2 +- src/propChecker.js | 4 ++- 13 files changed, 85 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 13de8fdc..57423265 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a React Native Date Picker with following main features: 📱  Supports iOS, Android and Expo
-🕑  3 different modes: Time, Date, DateTime
+🕑  4 different modes: Time, Date, DateTime, YearAndMonth
🌍  Various languages
🎨  Customizable
🖼  Modal or Inlined
@@ -181,7 +181,7 @@ export default () => { | `maximumDate` | Maximum selectable date.
Example: `new Date("2021-12-31")` | | `minimumDate` | Minimum selectable date.
Example: `new Date("2021-01-01")` | | `minuteInterval` | The interval at which minutes can be selected. | Date picker minute interval IOS | Date picker minute interval Android | -| `mode` | The date picker mode. `"datetime"`, `"date"`, `"time"` | React native date time pickerReact native datepickerReact native time picker | react native date time picker androidreact native datepicker androidreact native time picker android | +| `mode` | The date picker mode. `"datetime"`, `"date"`, `"time"`, `"yearAndMonth"` | React native date time pickerReact native datepickerReact native time picker | react native date time picker androidreact native datepicker androidreact native time picker android | | `locale` | The locale for the date picker. Changes language, date order and am/pm preferences. Value needs to be a Locale ID. | React Native Date picker locale language ios | React Native Date picker locale language android | | `timeZoneOffsetInMinutes` | Timezone offset in minutes (default: device's timezone) | | `is24hourSource` | Change how the 24h mode (am/pm) should be determined, by device settings or by locale. {'locale', 'device'} (android only, default: 'device') | @@ -227,7 +227,7 @@ On iOS the 12/24h preference is determined by the `locale` prop. Set for instanc ### Is it possible to show only month and year? -This is unfortunately not possible due to the limitation in DatePickerIOS. You should be able to create your own month-year picker with for instance https://github.com/TronNatthakorn/react-native-wheel-pick. +Yes! You can use the `"yearAndMonth"` mode which displays only month and year selection on both platforms. ### Why does the Android app crash in production? @@ -253,9 +253,9 @@ const [state, setState] = useState("idle") ``` -## Three different modes +## Different picker modes -Here are some more info about the three different picker modes that are available. +Here are some more info about the different picker modes that are available. ### Date time picker @@ -320,6 +320,23 @@ Set mode property to `time` to show the time picker: /> ``` +### Month Year picker + +The yearAndMonth mode allows you to select only month and year, which is useful for selecting birth months, credit card expiration dates, or other month/year selections. This mode uses the native `UIDatePickerModeYearAndMonth`. + +Set mode property to `yearAndMonth` to show the month year picker: + +```jsx + +``` + +**Compatibility Notes:** +- **iOS**: Displays the native year and month picker (`UIDatePickerModeYearAndMonth`) +- **Android**: Displays year and month wheels (without the date wheel) + ## About React Native Date Picker is a cross platform component for iOS and Android. It uses native code from respective platform to get the genuine look and feel the users expect. A strong motivation for creating this picker was the datetime mode on Android. It's quite unique for the platform and avoids two different picker popups, which normally is necessary. Instead, this datetime mode requires fewer user actions and enables a great user-experience. diff --git a/android/src/main/java/com/henninghall/date_picker/DerivedData.java b/android/src/main/java/com/henninghall/date_picker/DerivedData.java index 9612238e..68968f58 100644 --- a/android/src/main/java/com/henninghall/date_picker/DerivedData.java +++ b/android/src/main/java/com/henninghall/date_picker/DerivedData.java @@ -39,6 +39,11 @@ public ArrayList getVisibleWheels() { visibleWheels.add(WheelType.DATE); break; } + case yearAndMonth: { + visibleWheels.add(WheelType.YEAR); + visibleWheels.add(WheelType.MONTH); + break; + } } if((mode == Mode.time || mode == Mode.datetime) && state.derived.usesAmPm()){ visibleWheels.add(WheelType.AM_PM); diff --git a/android/src/main/java/com/henninghall/date_picker/models/Mode.java b/android/src/main/java/com/henninghall/date_picker/models/Mode.java index 041b6fa0..c438372f 100644 --- a/android/src/main/java/com/henninghall/date_picker/models/Mode.java +++ b/android/src/main/java/com/henninghall/date_picker/models/Mode.java @@ -1,5 +1,5 @@ package com.henninghall.date_picker.models; public enum Mode { - date, time, datetime + date, time, datetime, yearAndMonth } diff --git a/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java b/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java index a65399b2..e3abc2a2 100644 --- a/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java +++ b/android/src/main/java/com/henninghall/date_picker/ui/Wheels.java @@ -100,10 +100,24 @@ private String getDateModeString(int daysToSubtract) { return sb.toString(); } + private String getYearAndMonthString(int daysToSubtract) { + ArrayList wheels = getOrderedVisibleWheels(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 2; i++) { + if (i != 0) sb.append(" "); + Wheel w = wheels.get(i); + sb.append(w.getValue()); + } + return sb.toString(); + } + private String getDateString(int daysToSubtract){ if(state.getMode() == Mode.date ){ return getDateModeString(daysToSubtract); } + if(state.getMode() == Mode.yearAndMonth ){ + return getYearAndMonthString(daysToSubtract); + } return dayWheel.getValue(); } @@ -170,6 +184,10 @@ private String getDateFormatPattern(){ + wheels.get(1).getFormatPattern() + " " + wheels.get(2).getFormatPattern(); } + if(state.getMode() == Mode.yearAndMonth){ + return wheels.get(0).getFormatPattern() + " " + + wheels.get(1).getFormatPattern(); + } return dayWheel.getFormatPattern(); } diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java index 4724d957..a183269c 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/HourWheel.java @@ -43,7 +43,7 @@ public String toDisplayValue(String value) { @Override public boolean visible() { - return state.getMode() != Mode.date; + return state.getMode() != Mode.date && state.getMode() != Mode.yearAndMonth; } @Override diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/MinutesWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/MinutesWheel.java index 8afa3fc1..9446060a 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/MinutesWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/MinutesWheel.java @@ -32,7 +32,7 @@ public ArrayList getValues() { @Override public boolean visible() { - return state.getMode() != Mode.date; + return state.getMode() != Mode.date && state.getMode() != Mode.yearAndMonth; } @Override diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/MonthWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/MonthWheel.java index 4eb3a500..6c0c60d0 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/MonthWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/MonthWheel.java @@ -28,7 +28,7 @@ public ArrayList getValues() { @Override public boolean visible() { - return state.getMode() == Mode.date; + return state.getMode() == Mode.date || state.getMode() == Mode.yearAndMonth; } @Override diff --git a/android/src/main/java/com/henninghall/date_picker/wheels/YearWheel.java b/android/src/main/java/com/henninghall/date_picker/wheels/YearWheel.java index 41e0427a..fb45ffb8 100644 --- a/android/src/main/java/com/henninghall/date_picker/wheels/YearWheel.java +++ b/android/src/main/java/com/henninghall/date_picker/wheels/YearWheel.java @@ -56,7 +56,7 @@ private int getStartYear() { @Override public boolean visible() { - return state.getMode() == Mode.date; + return state.getMode() == Mode.date || state.getMode() == Mode.yearAndMonth; } @Override diff --git a/index.d.ts b/index.d.ts index 0ab796f6..761495cf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,7 +34,7 @@ export interface DatePickerProps extends ViewProps { /** * The date picker mode. */ - mode?: 'date' | 'time' | 'datetime' + mode?: 'date' | 'time' | 'datetime' | 'yearAndMonth' /** * Date change handler. diff --git a/ios/RNDatePicker.mm b/ios/RNDatePicker.mm index 7ec2dd20..fd9ce2e8 100644 --- a/ios/RNDatePicker.mm +++ b/ios/RNDatePicker.mm @@ -135,6 +135,13 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & if(newViewProps.mode == RNDatePickerMode::Time) [_picker setDatePickerMode:UIDatePickerModeTime]; if(newViewProps.mode == RNDatePickerMode::Date) [_picker setDatePickerMode:UIDatePickerModeDate]; if(newViewProps.mode == RNDatePickerMode::Datetime) [_picker setDatePickerMode:UIDatePickerModeDateAndTime]; + if(newViewProps.mode == RNDatePickerMode::YearAndMonth) { + if (@available(iOS 17.4, *)) { + [_picker setDatePickerMode:UIDatePickerModeYearAndMonth]; + } else { + [_picker setDatePickerMode:(UIDatePickerMode)4269]; // magic number from iOS team + } + } // We need to set minuteInterval after setting datePickerMode, otherwise minuteInterval is invalid in time mode. _picker.minuteInterval = _reactMinuteInterval; } diff --git a/ios/RNDatePickerManager.mm b/ios/RNDatePickerManager.mm index da42873c..6773a020 100644 --- a/ios/RNDatePickerManager.mm +++ b/ios/RNDatePickerManager.mm @@ -9,12 +9,29 @@ @implementation RCTConvert(UIDatePicker) -RCT_ENUM_CONVERTER(UIDatePickerMode, (@{ - @"time": @(UIDatePickerModeTime), - @"date": @(UIDatePickerModeDate), - @"datetime": @(UIDatePickerModeDateAndTime), - @"countdown": @(UIDatePickerModeCountDownTimer), // not supported yet -}), UIDatePickerModeTime, integerValue) ++ (UIDatePickerMode)UIDatePickerMode:(id)json +{ + static NSDictionary *modes; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + modes = @{ + @"time": @(UIDatePickerModeTime), + @"date": @(UIDatePickerModeDate), + @"datetime": @(UIDatePickerModeDateAndTime), + @"countdown": @(UIDatePickerModeCountDownTimer), // not supported yet + }; + }); + + if ([json isEqualToString:@"yearAndMonth"]) { + if (@available(iOS 17.4, *)) { + return (UIDatePickerModeYearAndMonth); + } else { + return (UIDatePickerMode)4269; + } + } + + return (UIDatePickerMode)[RCTConvertEnumValue("UIDatePickerMode", modes, @(UIDatePickerModeTime), json) integerValue]; +} @end diff --git a/npmREADME.md b/npmREADME.md index 374261bb..e899a0bb 100644 --- a/npmREADME.md +++ b/npmREADME.md @@ -1,6 +1,6 @@ # React Native Date Picker -A cross platform react native date picker component for android and ios. It includes 3 different modes: date, time, and datetime. The date picker is customizable and has multiple language support. +A cross platform react native date picker component for android and ios. It includes 4 different modes: date, time, datetime, and yearAndMonth. The date picker is customizable and has multiple language support. ## Modal diff --git a/src/propChecker.js b/src/propChecker.js index e2723f86..cc736eb5 100644 --- a/src/propChecker.js +++ b/src/propChecker.js @@ -53,7 +53,9 @@ const heightCheck = new PropCheck( const modeCheck = new PropCheck( (props) => - props && props.mode && !['datetime', 'date', 'time'].includes(props.mode), + props && + props.mode && + !['datetime', 'date', 'time', 'yearAndMonth'].includes(props.mode), "Invalid mode. Valid modes: 'datetime', 'date', 'time'" ) From e88fc0138d9c6711bb78e6a23460cb043f98ea80 Mon Sep 17 00:00:00 2001 From: GeoffreySautieres Date: Mon, 13 Oct 2025 22:57:00 +0200 Subject: [PATCH 2/3] fix(ios): remove modules provider to be able to launch the app --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 469386dc..36460e4e 100644 --- a/package.json +++ b/package.json @@ -70,9 +70,6 @@ "ios": { "componentProvider": { "RNDatePicker": "RNDatePicker" - }, - "modulesProvider": { - "RNDatePicker": "RNDatePickerManager" } } } From 07ca1150164a2da2875090da53bef1876f13bd8b Mon Sep 17 00:00:00 2001 From: GeoffreySautieres Date: Fri, 17 Oct 2025 18:24:13 +0200 Subject: [PATCH 3/3] fix(DatePickerIOS): linter issue --- src/DatePickerIOS.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DatePickerIOS.js b/src/DatePickerIOS.js index 987cbcac..268519bc 100644 --- a/src/DatePickerIOS.js +++ b/src/DatePickerIOS.js @@ -23,8 +23,12 @@ export const DatePickerIOS = (props) => { style: [styles.datePickerIOS, props.style], date: props.date ? props.date.toISOString() : undefined, locale: props.locale ? props.locale : undefined, - maximumDate: props.maximumDate ? props.maximumDate.toISOString() : undefined, - minimumDate: props.minimumDate ? props.minimumDate.toISOString() : undefined, + maximumDate: props.maximumDate + ? props.maximumDate.toISOString() + : undefined, + minimumDate: props.minimumDate + ? props.minimumDate.toISOString() + : undefined, theme: props.theme ? props.theme : 'auto', }