Skip to content

Commit 90f0d20

Browse files
feat: support timezone offset on Android (#396)
* Fix for adding Android timeoffset * Fixd * Fixed the bad constant * new Co-authored-by: bbell <[email protected]>
1 parent b46ad76 commit 90f0d20

File tree

9 files changed

+75
-5
lines changed

9 files changed

+75
-5
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ React Native date & time picker component for iOS, Android and Windows.
5252
- [`value` (`required`)](#value-required)
5353
- [`maximumDate` (`optional`)](#maximumdate-optional)
5454
- [`minimumDate` (`optional`)](#minimumdate-optional)
55-
- [`timeZoneOffsetInMinutes` (`optional`, `iOS only`)](#timezoneoffsetinminutes-optional-ios-only)
55+
- [`timeZoneOffsetInMinutes` (`optional`, `iOS or Android only`)](#timezoneoffsetinminutes-optional-ios-only)
5656
- [`timeZoneOffsetInSeconds` (`optional`, `Windows only`)](#timezoneoffsetinsecond-optional-windows-only)
5757
- [`dayOfWeekFormat` (`optional`, `Windows only`)](#dayOfWeekFormat-optional-windows-only)
5858
- [`dateFormat` (`optional`, `Windows only`)](#dateFormat-optional-windows-only)
@@ -257,7 +257,7 @@ Defines the minimum date that can be selected. Note that on Android, this only w
257257
<RNDateTimePicker minimumDate={new Date(1950, 0, 1)} />
258258
```
259259

260-
#### `timeZoneOffsetInMinutes` (`optional`, `iOS only`)
260+
#### `timeZoneOffsetInMinutes` (`optional`, `iOS and Android only`)
261261

262262
Allows changing of the timeZone of the date picker. By default it uses the device's time zone.
263263

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

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public final class RNConstants {
99
public static final String ARG_IS24HOUR = "is24Hour";
1010
public static final String ARG_DISPLAY = "display";
1111
public static final String ARG_NEUTRAL_BUTTON_LABEL = "neutralButtonLabel";
12+
public static final String ARG_TZOFFSET_MIN = "timeZoneOffsetInMinutes";
1213
public static final String ACTION_DATE_SET = "dateSetAction";
1314
public static final String ACTION_TIME_SET = "timeSetAction";
1415
public static final String ACTION_DISMISSED = "dismissedAction";

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

+6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package com.reactcommunity.rndatetimepicker;
22

33
import java.util.Calendar;
4+
import java.util.TimeZone;
45
import android.os.Bundle;
56

67
public class RNDate {
78
private Calendar now;
89

910
public RNDate(Bundle args) {
1011
now = Calendar.getInstance();
12+
if (args != null && args.containsKey(RNConstants.ARG_TZOFFSET_MIN)) {
13+
now.setTimeZone(TimeZone.getTimeZone("GMT"));
14+
Integer timeZoneOffsetInMinutes = args.getInt(RNConstants.ARG_TZOFFSET_MIN);
15+
now.add(Calendar.MILLISECOND, timeZoneOffsetInMinutes * 60000);
16+
}
1117

1218
if (args != null && args.containsKey(RNConstants.ARG_VALUE)) {
1319
set(args.getLong(RNConstants.ARG_VALUE));

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

+3
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ private Bundle createFragmentArguments(ReadableMap options) {
148148
if (options.hasKey(RNConstants.ARG_INTERVAL) && !options.isNull(RNConstants.ARG_INTERVAL)) {
149149
args.putInt(RNConstants.ARG_INTERVAL, options.getInt(RNConstants.ARG_INTERVAL));
150150
}
151+
if (options.hasKey(RNConstants.ARG_TZOFFSET_MIN) && !options.isNull(RNConstants.ARG_TZOFFSET_MIN)) {
152+
args.putInt(RNConstants.ARG_TZOFFSET_MIN, options.getInt(RNConstants.ARG_TZOFFSET_MIN));
153+
}
151154
return args;
152155
}
153156
}

example/App.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const MINUTE_INTERVALS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30];
6060

6161
export const App = () => {
6262
const [date, setDate] = useState(new Date(1598051730000));
63+
const [tzOffsetInMinutes, setTzOffsetInMinutes] = useState(0);
6364
const [mode, setMode] = useState(MODE_VALUES[0]);
6465
const [show, setShow] = useState(false);
6566
const [color, setColor] = useState();
@@ -223,10 +224,20 @@ export const App = () => {
223224
title="hide picker"
224225
/>
225226
</View>
227+
<View style={styles.button}>
228+
<Button
229+
testID="setTz"
230+
onPress={() => {
231+
setTzOffsetInMinutes(60);
232+
setShow(true);
233+
}}
234+
title="setTz"
235+
/>
236+
</View>
226237
{show && (
227238
<DateTimePicker
228239
testID="dateTimePicker"
229-
timeZoneOffsetInMinutes={0}
240+
timeZoneOffsetInMinutes={tzOffsetInMinutes}
230241
minuteInterval={interval}
231242
value={date}
232243
mode={mode}

example/e2e/detoxTest.spec.js

+28
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,34 @@ describe('Example', () => {
130130
}
131131
});
132132

133+
async function userOpensPickerTz({mode, display, interval}) {
134+
await element(by.text(mode)).tap();
135+
await element(by.text(display)).tap();
136+
if (interval) {
137+
await element(by.text(String(interval))).tap();
138+
}
139+
await element(by.id('setTz')).tap();
140+
}
141+
142+
it('setTz should change time text when time changes 60 minutes', async () => {
143+
await userOpensPickerTz({mode: 'time', display: 'default'});
144+
const timeText = getTimeText();
145+
146+
if (isIOS()) {
147+
const testElement = getDateTimePickerIOS();
148+
await testElement.setColumnToValue(0, '2');
149+
await testElement.setColumnToValue(1, '44');
150+
await testElement.setColumnToValue(2, 'PM');
151+
152+
await expect(timeText).toHaveText('13:44');
153+
} else {
154+
await userChangesMinuteValue();
155+
await userTapsOkButtonAndroid();
156+
157+
await expect(timeText).toHaveText('22:30');
158+
}
159+
});
160+
133161
it(':android: given we specify neutralButtonLabel, tapping the corresponding button sets date to the beginning of the unix time epoch', async () => {
134162
await elementById('neutralButtonLabelTextInput').typeText('clear');
135163
await userOpensPicker({mode: 'time', display: 'default'});

src/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const DISPLAY_DEFAULT = 'default';
1010
export const DISPLAY_SPINNER = 'spinner';
1111
export const DISPLAY_CLOCK = 'clock';
1212
export const DISPLAY_CALENDAR = 'calendar';
13+
export const MIN_MS = 60000;
1314

1415
// TODO vonovak potentially replace the above string consts with this object
1516
export const ANDROID_DISPLAY = Object.freeze({

src/datetimepicker.android.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
NEUTRAL_BUTTON_ACTION,
1313
ANDROID_DISPLAY,
1414
ANDROID_MODE,
15+
MIN_MS,
1516
} from './constants';
1617
import pickers from './picker';
1718
import invariant from 'invariant';
@@ -38,6 +39,7 @@ function getPicker({
3839
maximumDate,
3940
neutralButtonLabel,
4041
minuteInterval,
42+
timeZoneOffsetInMinutes,
4143
}) {
4244
switch (mode) {
4345
case MODE_TIME:
@@ -47,6 +49,7 @@ function getPicker({
4749
minuteInterval,
4850
is24Hour,
4951
neutralButtonLabel,
52+
timeZoneOffsetInMinutes,
5053
});
5154
case MODE_DATE:
5255
default:
@@ -72,6 +75,7 @@ export default function RNDateTimePicker(props: AndroidNativeProps) {
7275
maximumDate,
7376
neutralButtonLabel,
7477
minuteInterval,
78+
timeZoneOffsetInMinutes,
7579
} = props;
7680
const valueTimestamp = value.getTime();
7781

@@ -92,11 +96,12 @@ export default function RNDateTimePicker(props: AndroidNativeProps) {
9296
maximumDate,
9397
neutralButtonLabel,
9498
minuteInterval,
99+
timeZoneOffsetInMinutes,
95100
});
96101

97102
picker.then(
98103
function resolve({action, day, month, year, minute, hour}) {
99-
const date = new Date(valueTimestamp);
104+
let date = new Date(valueTimestamp);
100105
const event: AndroidEvent = {
101106
type: 'set',
102107
nativeEvent: {},
@@ -109,7 +114,14 @@ export default function RNDateTimePicker(props: AndroidNativeProps) {
109114
break;
110115

111116
case TIME_SET_ACTION:
112-
event.nativeEvent.timestamp = date.setHours(hour, minute);
117+
date.setHours(hour, minute);
118+
if (timeZoneOffsetInMinutes !== undefined) {
119+
const offset =
120+
date.getTimezoneOffset() * MIN_MS +
121+
timeZoneOffsetInMinutes * MIN_MS;
122+
date = new Date(date.getTime() - offset);
123+
}
124+
event.nativeEvent.timestamp = date;
113125
onChange(event, date);
114126
break;
115127

src/types.js

+8
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ export type AndroidNativeProps = $ReadOnly<{|
136136
*/
137137
display: Display,
138138

139+
/**
140+
* Timezone offset in minutes.
141+
*
142+
* By default, the date picker will use the device's timezone. With this
143+
* parameter, it is possible to force a certain timezone offset. For
144+
* instance, to show times in Pacific Standard Time, pass -7 * 60.
145+
*/
146+
timeZoneOffsetInMinutes?: ?number,
139147
/**
140148
* The interval at which minutes can be selected.
141149
*/

0 commit comments

Comments
 (0)