Date picker: timezone shift
Asked Answered
C

4

12

Problem

If I select a date in a <DateInput>, the datetime is in the user's timezone (e.g. 2017-07-01T00:00:00.000+02:00) but when it's sent to the server, it's transformed to UTC and thus ends up as 2017-06-30T22:00:00.000Z, one day behind.

The underlaying back-end is unaware of the user's timezone and cannot shift the dates back so after stripping the time it ends up with wrong date.

What would solve my problem

Any of these options would work fine:

  1. The datetime is sent with the user's timezone.
  2. The datetime is sent as naive (=without timezone).
  3. The date selected by the user is already treated as UTC.
  4. A date only (2017-07-01) instead of the ISO datetime is sent.

What I tried

  • I looked at Admin-on-rest's DateInput documentation and haven't found any option to alter the behaviour.
  • I looked at the related Material-UI's documentation and the only somewhat relevant option appears to be DateTimeFormat but despite a couple of tries I got nowhere.
  • I checked other answers, e.g Material UI Time Picker UTC but cannot figure out how to apply the suggested solution.
Cranial answered 5/7, 2017 at 10:34 Comment(0)
L
7

Material UI date and timepickers return the local values. You can write a function in their onChange function to reformat the date to any value.

  handleTimeInput = (event, time) => {
    this.setState({
      time: time
    })
  }

You can then reset the time you send to server by using a simple function like the one below to convert local time to UTC

export default (date) => {
  return new Date(date.getTime() + date.getTimezoneOffset()*60000)
}

(for displaying)

export default (date) => {
  const tempDate = new Date(date)
  return new Date(tempDate.getTime() - tempDate.getTimezoneOffset()*60000)
}

This is the entire component code for my comp.

class DateTimePicker extends Component {
  constructor(props) {
    super(props);
    this.state = {
      date: null,
      time: null
    };
  }
  handleDateInput = (event, date) => {
    this.setState({
      date: date
    })
  }
  handleTimeInput = (event, time) => {
    this.setState({
      time: time
    })
  }
  componentDidUpdate(prevProps, prevState) {
    const {setSchedule, channel} = this.props
    if (prevState.date !== this.state.date || prevState.time !== this.state.time) {
      if (this.state.date && this.state.time) {
        setSchedule(convertISTtoUTC(concatenateDateAndTime(this.state.date, this.state.time)), channel)
      }
    }
  }
  render() {
    return (
      <div >
          <DatePicker autoOk={true} onChange={this.handleDateInput} />
          <TimePicker autoOk={true} pedantic={true} onChange={this.handleTimeInput} />
      </div>
    )
  }
}

With this you have 2 options.

1) you can hook this to redux directly and shoot actions when both are selected.

https://marmelab.com/admin-on-rest/Actions.html

2) Write your own Input component

https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component

Ladle answered 5/7, 2017 at 12:53 Comment(3)
Thanks for your input. It looks like a move in the right direction. What I don't understand though is how to apply this transformation in Admin-on-rest. I have to admit that this very different field of work than I usually do.Cranial
Editing my answerLadle
At the end I've settled on a different approach but thanks again for your valuable suggestions.Cranial
C
1

The easiest option is to set a custom function as the parse attribute of <DateInput> which allows to manipulate the value before going into the Redux storage.

const dateString = v => {
  if (isNaN(v)) return;

  let parsedDate = new Date(v);
  let adjustedDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000);

  return adjustedDate;
};

<DateInput source="your_date" parse={dateString} />

This is described in the Admin-on-rest documentation and further information can be found in the Redux form documentation on Value Lifecycle Hooks.

NOTE: Credit goes to kunal pareek for his suggestion on how to fix the timezone offset in the other advanced answer.

UPDATE: This might not be working correctly in the current version (as of 08/2018) of Admin-on-rest. See answer by Tomasz Madeyski and/or check the documentation in which the problem has been addressed.

Cranial answered 6/7, 2017 at 16:5 Comment(1)
This is almost working, check my answer hereArad
A
0

Well, since editing should not change author's intention and it's a bit too long for an edit I'm adding my own answer basing on tmt answer:

Option to use parse part is ok but function itself is faulty (see my comments in code to see why):

const dateString = v => {
  /* 
  * since v parameter coming is a string then you cannot use isNan 
  * undefined is also coming as a parameter so !v is good equivalent
  */
  if (!v) return; 

  let parsedDate = new Date(v);
  let adjustedDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000);

  /*
  * since parameter coming in is a string you need to pass string further, that's why I added toISOString() call
  */
  return adjustedDate.toISOString();
};

<DateInput source="your_date" parse={dateString} />
Arad answered 3/8, 2018 at 7:39 Comment(1)
Apparently there have been changes and the problem has been addressed in Admin-on-rest. They now offer their own solution: marmelab.com/admin-on-rest/Inputs.html#dateinputCranial
E
0

Just set moment to specific timezone and intialize material ui picker with moment, and da da.. it will work..

import moment from 'moment-timezone';
import MomentUtils from '@date-io/moment';
import {DatePicker, MuiPickersUtilsProvider} from '@material-ui/pickers';

useEffect(()=>{
    moment.tz.setDefault('Asia/Kolkata');
},[])

<MuiPickersUtilsProvider libInstance={moment} utils={MomentUtils}>
               <DatePicker
                  variant='inline'
                  autoOk
                  label=''
                  disableToolbar
                  disableFuture
                  format='MM/DD/YYYY'
                  value={selectedDay}
                  onChange={(day) => {
                    const date = moment(day).format('MM/DD/YYYY');
                    setCurrentDay(date);
                    handleDayChange(day);
                  }}
                  InputProps={{
                    disableUnderline: true,
                    style: {
                      backgroundColor: 'white',
                      paddingLeft: 10,
                      borderRadius: 5,
                      width: 160,
                    },
                    endAdornment: (
                      <InputAdornment position='end'>
                        <CalendarTodayIcon style={{paddingRight: 8}} />
                      </InputAdornment>
                    ),
                  }}
                />
              </MuiPickersUtilsProvider>
Entozoic answered 18/6, 2021 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.