Set timezone React-Datepicker - reactjs

I'm using the react-datepicker to let user select a date. However, right now it uses local time (PDT), but I want to hardcode it to use a specific timezone (PST).
I tried using utcOffset prop but it doesn't seem to be doing anything. Does anyone know how to accomplish this?

For my part I set the defaultTimezone before rendering the React-dates plugin.
React-dates will just use the default timezone.
moment.tz.setDefault('America/Los_Angeles');

This works for me:
import React, { ComponentProps } from "react"
import DatePicker from "react-datepicker"
import moment from "moment"
interface Props {
timezone: string
}
const DatePickerWithTimezone = ({
selected,
onChange,
timezone,
...props
}: Props & ComponentProps<typeof DatePicker>) => (
<DatePicker
selected={selected ? setLocalZone(selected, timezone) : null}
onChange={(v, e) => {
onChange(v ? setOtherZone(v, timezone) : null, e)
}}
{...props}
/>
)
const setLocalZone = (date: Date, timezone: string) => {
const dateWithoutZone = moment
.tz(date, timezone)
.format("YYYY-MM-DDTHH:mm:ss.SSS")
const localZone = moment(dateWithoutZone).format("Z")
const dateWithLocalZone = [dateWithoutZone, localZone].join("")
return new Date(dateWithLocalZone)
}
const setOtherZone = (date: Date, timezone: string) => {
const dateWithoutZone = moment(date).format("YYYY-MM-DDTHH:mm:ss.SSS")
const otherZone = moment.tz(date, timezone).format("Z")
const dateWithOtherZone = [dateWithoutZone, otherZone].join("")
return new Date(dateWithOtherZone)
}
export default DatePickerWithTimezone

since datepicker doesn't use moment.js anymore i tried to implement a hacky solution for this issue, assuming the initial value is a string for instance:
export const formatUTC = (dateInt, addOffset = false) => {
let date = (!dateInt || dateInt.length < 1) ? new Date : new Date(dateInt);
if (typeof dateInt === "string") {
return date;
} else {
const offset = addOffset ? date.getTimezoneOffset() : -(date.getTimezoneOffset());
const offsetDate = new Date();
offsetDate.setTime(date.getTime() + offset * 60000)
return offsetDate;
}
}
inside date i call the formatter like this:
selected={formatUTC(this.props.input.value,true)}
onChange={date => formatUTC(date)}

I've been thru this, If you decided that you want to just ignore your local offset then you can hardcode the zone.
Observation just to give a complete answer: PST will always be -08:00, but if you want for example pacific time, right now is -07:00, in this case, you may want to install 'moment.timezone' then import moment from 'moment-timezone' and just get the current offset with moment.tz('US/Pacific').format('Z')
The code in typescript (I can change it to Javascript if you want):
interface ICalendarInputProps {
handleChange: (newDate: moment.Moment) => void;
}
const CalendarInput = ({ handleChange }: ICalendarInputProps) => {
const onChange = (date: Date) => {
handleChange(moment(`${moment(date).format('YYYY-MM-DDThh:mm:ss')}-08:00`));
// This is to get the offset from a timezone: handleChange(moment(`${moment(date).format('YYYY-MM-DDThh:mm:ss')}${moment.tz('US/Pacific').format('Z')}`));
};
return (<DatePicker onChange={onChange} />);
};
export default CalendarInput;

This component outputs Date objects set to midnight local-time at the start of the chosen day. This is a problem. If there is a way of configuring this behaviour, I haven't found it.
The only way to stay sane when dealing with dates is to make sure that your dates are always midnight UTC at the start of the date in question. To get this behaviour from react-datepicker, the only thing I've found is to subtract the timezone offset from the output...
interface IProps {
value: any
setValue: (value: Date) => void
}
const DayPicker: FC<IProps> = ({ value, setValue, placeholderText = "", minDate = new Date() }) => {
function correctToUtcThenSet(val: Date) {
setValue(new Date(val.getTime() - val.getTimezoneOffset() * 60000))
}
return <DatePicker
onChange={correctToUtcThenSet}
selected={value}
/>
}

Other answers didn't work as I'd hoped, and sometimes the dates were off by 1 day because of time zone differences.
This is what I needed:
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import { getEndOfDayUtc, treatLocalDateAsUtcMidnight, treatUtcMidnightAsLocalDate } from '../../../shared/helpers/datetime';
type DatePickerUtcProps = {
selected: Date | string;
onChange: any;
isEndOfDay: boolean;
};
function getSelectedAsLocal(selected: Date | string): Date {
const selectedDate = typeof selected === 'string' ? new Date(selected) : selected;
return treatUtcMidnightAsLocalDate(selectedDate);
}
export function DatePickerUtc({ selected, onChange, isEndOfDay, ...datePickerProps }: DatePickerUtcProps): JSX.Element {
function onChangeAsUtc(local: Date) {
const utc = treatLocalDateAsUtcMidnight(local);
const adjusted = isEndOfDay ? getEndOfDayUtc(utc) : utc;
console.log('onChangeAsUtc', { local, utc, adjusted, isEndOfDay });
onChange(adjusted);
}
return <DatePicker onChange={onChangeAsUtc} selected={getSelectedAsLocal(selected)} {...datePickerProps} />;
}
export function treatLocalDateAsUtcMidnight(localDate: Date): Date {
const moment = dayjs(localDate).tz('UTC', true); // https://day.js.org/docs/en/plugin/timezone
const utcMidnight = getStartOfDayUtc(moment.toDate());
console.log({ localDate, utcMidnight });
return utcMidnight;
}
export function treatUtcMidnightAsLocalDate(utcMidnight: Date): Date {
const sliceOfJustTheDatePart = utcMidnight.toISOString().slice(0, 10);
const localDate = dayjs(sliceOfJustTheDatePart).toDate();
console.log({ localDate, sliceOfJustTheDatePart, utcMidnight });
return localDate;
}
From: <DatePickerUtc selected={startDate} onChange={(utcDate: Date) => setStartDate(utcDate)} {...datePickerProps} />
To: <DatePickerUtc selected={endDate} onChange={(utcDate: Date) => setEndDate(utcDate)} {...datePickerPropsEndOfDay} />

I also didn't have luck with utcOffset. You could use moment-timezone in your project instead of moment and convert it yourself:
import moment from "moment-timezone";
onDateChange = date => {
console.log("as is:", date.format("YYYY-MM-DD HH:mm:ss"));
console.log("PST:", moment(date).tz("America/Los_Angeles").format("YYYY-MM-DD HH:mm:ss"));
};
Sandbox: https://codesandbox.io/s/y2vwj9mwpz

Since you're using moment.js, you can try using moment.utc() and subtract hours to pst timezone.
moment.utc().startOf('day').subtract(8, 'hours')

Related

date picker component not changing date using onChange

I am trying to use react-datepicker package to create my own date picker component, i have modified some of the basic stuff to have a control on it and it all seems to work but my onChange doesn't change the date on the view... i can see the log the onChange does fire but nothing happens when i choose a new date on screen. Am fairly new and trying to understand what i did wrong.
Here is my DateTimePicker.tsx
import type { FC } from 'react';
import React, { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import './DateTimePicker.css';
export interface Props {
/**
* Determines format of date to be displayed
* tag i.e. format=dd/MM/yyyy => 24/12/2020
*/
format?: 'dd/MM/yyyy' | 'mm/dd/yyyy' | 'yyyy/MM/dd' | 'yyyy/dd/MM';
/** on change method updates the date input field with selected date */
onChange(date: Date | null, event: React.SyntheticEvent<any, Event> | undefined): void;
/** Determine the type of dropdown of year and month selecter */
mode?: 'select' | 'scroll';
/** Placeholder for no date is selected */
placeholder?: string;
}
/**
* Component that serves as an datepicker input field to let the user select Date
*
* #return DateTimePicker component
*/
export const DateTimePicker: FC<Props> = ({
format = 'dd/MM/yyyy',
mode = 'select',
placeholder = 'Click to add a date',
onChange,
}) => {
return (
<DatePicker
className="apollo-component-library-date-picker-component"
placeholderText={placeholder}
dateFormat={format}
onChange={onChange}
dropdownMode={mode}
showMonthDropdown
showYearDropdown
adjustDateOnChange
/>
);
};
react-datepicker takes a selected prop that keeps track of the currently selected date. You'll need to manage it in state and provide it to the component:
const Example = () => {
const [startDate, setStartDate] = useState(new Date());
return (
<DatePicker selected={startDate} onChange={(date) => setStartDate(date)} />
);
};

Getting 'objects are not a valid React child' error when attempting to render date in React web app

I am attempting to pull a string integer from my database, convert it into a number, then transform that number into a date.
However, I keep getting the following error every time I load my page when live.
Objects are not valid as a React child (found: Mon Dec 14 2020
10:48:11 GMT+0000 (GMT)). If you meant to render a collection of
children, use an array instead.
The date variables are working exactly as expected, I just want to now render them in my app, which is where the problem is occurring.
Can someone point out where I'm going wrong here?
Here's my full component....
import React, { Component } from 'react';
import axios from 'axios';
import './individual-debts.css';
import IndividualDebtCard from '../individual-debts/individual-debt-card';
class IndividualDebts extends Component {
constructor(props) {
super(props)
this.state = {
debts: []
}
}
componentDidMount() {
axios.get("/api/fetch/fetchDebtCards")
.then((response) => {
this.setState({
debts: response.data,
fetchInProgress: false
})
})
})
}
render() {
const fetchDebts = this.state.debts.map (debt => {
const dayCurrent = new Date();
const dayFromDB = parseInt(debt.date);
const dayFromDBDate = new Date(new Date().setDate(dayFromDB));
const variableDayFromDB = new Date(new Date().setDate(dayFromDB));
const dayFromDBDatePlusOne = variableDayFromDB.setMonth(variableDayFromDB.getMonth()+1)
let dayToRender = ''
if (dayCurrent < dayFromDBDate) {
dayToRender = dayFromDBDate
console.log(dayToRender)
} else {
dayToRender = variableDayFromDB
console.log(dayToRender)
}
return (
<IndividualDebtCard key={debt._id}
monthly={debt.monthly}
repayment={dayToRender} />
)
})
return (
<div>
{fetchDebts}
</div>
)
}
}
export default IndividualDebts;
Any advice here would be really appreciated! Thank you.
Date instance is an object in JavaScript so it cant be passed as a children prop in react (since it accepts only a string or an element)
To solve this issue simply convert the Date instance to an ISOString (or any other format you like)
<IndividualDebtCard
key={debt._id}
monthly={debt.monthly}
repayment={dayToRender.toISOString()} />
If you are trying to render your date in a <p> (for example), it won't work as dates are Objects.
You could write a function to transform it to a string or use a lib like date-fns.
Here is a go at it:
export const DisplayDate = (date: Date | string | number): string => {
if (date) {
const dateAsDate = new Date(date);
const day =
dateAsDate.getDate() < 10
? `0${dateAsDate.getDate()}`
: dateAsDate.getDate();
const month =
dateAsDate.getMonth() < 9
? `0${dateAsDate.getMonth() + 1}`
: `${dateAsDate.getMonth() + 1}`;
const year = dateAsDate.getFullYear();
return `${day}-${month}-${year}`;
}
return '-';
};

Trying to calculate the difference between 2 time and dates using moment.js

I'm using a date/time picker that I found on https://material-ui-pickers.dev/ and wanted to use it with the moment.js library. I'm having some issues. I have a form that collects a start time and an end time. I wanted to use the moment.js "diff" method to calculate the difference in hours. I keep getting "20.4234254" or similar, regardless of what dates & times I enter.
The format of the date as it's being held in state and managed by moment.js is:
"Wed Jul 08 2020 21:51:23 GMT-0700 (Pacific Daylight Time)".
import React, { useState, useEffect } from 'react';
import MomentUtils from '#date-io/moment';
import {
DatePicker,
TimePicker,
DateTimePicker,
MuiPickersUtilsProvider,
} from '#material-ui/pickers';
import moment from 'moment';
function DateFormField() {
const [startDate, startTime] = useState(new Date());
const [endDate, endTime] = useState(new Date());
const [duration, setDuration] = useState("");
const timeCalc = (startDate, endDate) => {
var start = moment(startDate);
var end = moment(endDate);
console.log(end.diff(start, "hours", true) + ' hours');
}
return (
<MuiPickersUtilsProvider utils={MomentUtils}>
<DateTimePicker value={startDate} onChange={startTime} helperText="Start Time" />
<DateTimePicker value={endDate} onChange={endTime} helperText="End Time" />
<button onClick={timeCalc}>Time Duration: {duration}</button>
</MuiPickersUtilsProvider>
);
}
export default DateFormField;
Issue
I can't get this code to run in a sandbox (interacting with the DateTimePicker for some reason throws a utils.getYearText is not a function TypeError), but I think I know what the issue is.
You define timeCalc to consume two arguments, startDate and endDate
const timeCalc = (startDate, endDate) => {
var start = moment(startDate);
var end = moment(endDate);
console.log(end.diff(start, "hours", true) + ' hours');
}
but in the button to trigger it you simply attach the function to the onClick handler, so it is actually only passed the event object as the first argument and undefined for the second.
<button onClick={timeCalc}>Time Duration: {duration}</button>
I then tested timeCalc manually in the function body by passing it the startDate and endDate state values and it (presumably) correctly returns 0 hours, as would be expected from two nearly identical timestamps.
const timeCalc = (startDate, endDate) => {
var start = moment(startDate);
var end = moment(endDate);
console.log(end.diff(start, "hours", true) + " hours");
};
timeCalc(new Date(), new Date());
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"></script>
Solution
Either remove the arguments to timeCalc so the referenced startDate and endDate values will be the ones in global component scope
const timeCalc = () => {
var start = moment(startDate);
var end = moment(endDate);
console.log(end.diff(start, "hours", true) + ' hours');
}
Or explicitly pass the state values to the callback
<button onClick={() => timeCalc(startDate, endDate)}>
Time Duration: {duration}
</button>

React Custom Hook produce warning: Maximum update depth exceeded

I'm new to React Hooks and are taking the first steps... Any help appreciated! I want to re-use logic for sorting and transforming data sets before rendering in charts. So I split it into a Custom hook but get a warning and it seems to be in a re-render loop (slowly counting up)
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
I should have a dependency array and the dependencies only change on button click.. So I don't understand why it goes into a re-render loop...?
CigarettesDetailsContainer receives "raw" data in props and passes transformed data to a child component rendering the chart. It also handles changing dates from the child so I keep that state in here.
The useSingleValueChartData hook transforms the raw data and should re-run when changes to date and time period.
CigarettesDetailsContainer
import React, { FC, useState } from 'react'
import moment from 'moment'
import { ApiRegistration } from 'models/Api/ApiRegistration'
import { CigarettesDetails } from './layout'
import { useSingleValueChartData } from 'hooks/useSingleValueChartData'
import { TimePeriod } from 'models/TimePeriod'
interface Props {
registrations: ApiRegistration[]
}
const initialStart = moment()
.year(2018)
.week(5)
.startOf('isoWeek')
const initialEnd = initialStart.clone().add(1, 'week')
const initialPeriod = TimePeriod.Week
const CigarettesDetailsContainer: FC<Props> = ({ registrations }) => {
const [startDate, setStartDate] = useState(initialStart)
const [endDate, setEndDate] = useState(initialEnd)
const [timePeriod, setTimePeriod] = useState(initialPeriod)
const data = useSingleValueChartData(
registrations,
startDate.toDate(),
endDate.toDate(),
timePeriod
)
const handleTimeChange = (change: number) => {
let newStartDate = startDate.clone()
let newEndDate = endDate.clone()
switch (timePeriod) {
default:
newStartDate.add(change, 'week')
newEndDate.add(change, 'week')
break
}
setStartDate(newStartDate)
setEndDate(newEndDate)
}
return <CigarettesDetails onTimeChange={handleTimeChange} data={data} />
}
export default CigarettesDetailsContainer
useSingleValueChartData
import React, { useEffect, useState } from 'react'
import moment from 'moment'
import { ApiRegistration } from 'models/Api/ApiRegistration'
import { TimePeriod } from 'models/TimePeriod'
import { GroupedChartData, SingleValueChartData } from 'models/ChartData'
import { createWeekdaysList } from 'components/Core/Utils/dateUtils'
export function useSingleValueChartData(
registrations: ApiRegistration[],
startDate: Date,
endDate: Date,
timePeriod: TimePeriod = TimePeriod.Week
) {
const [data, setData] = useState<SingleValueChartData[]>([])
// used for filling chart data set with days without registrations
let missingWeekDays: string[] = []
useEffect(() => {
// which days are missing data
// eslint-disable-next-line react-hooks/exhaustive-deps
missingWeekDays = createWeekdaysList(startDate)
const filteredByDates: ApiRegistration[] = registrations.filter(reg =>
moment(reg.date).isBetween(startDate, endDate)
)
const filteredByDirtyValues = filteredByDates.filter(reg => reg.value && reg.value > -1)
const grouped: SingleValueChartData[] = Object.values(
filteredByDirtyValues.reduce(groupByWeekDay, {} as GroupedChartData<
SingleValueChartData
>)
)
const filled: SingleValueChartData[] = grouped.concat(fillInMissingDays())
const sorted: SingleValueChartData[] = filled.sort(
(a: SingleValueChartData, b: SingleValueChartData) =>
new Date(a.date).getTime() - new Date(b.date).getTime()
)
setData(sorted)
}, [startDate, timePeriod])
function groupByWeekDay(
acc: GroupedChartData<SingleValueChartData>,
{ date: dateStr, value }: { date: string; value?: number }
): GroupedChartData<SingleValueChartData> {
const date: string = moment(dateStr).format('YYYY-MM-DD')
acc[date] = acc[date] || {
value: 0,
}
acc[date] = {
date,
value: value ? acc[date].value + value : acc[date].value,
}
// remove day from list of missing week days
const rest = missingWeekDays.filter(d => d !== date)
missingWeekDays = rest
return acc
}
function fillInMissingDays(): SingleValueChartData[] {
return missingWeekDays.map(date => {
return {
value: 0,
date,
}
})
}
return data
}
In the custom hook, though you want to run the effect only on change of startDate or timePeriod, at present the effect is run everytime.
This is because how startDate and endDate params are being passed to custom hook.
const data = useSingleValueChartData(
registrations,
startDate.toDate(),
endDate.toDate(),
timePeriod
)
.toDate returns new date object.
So every time new date object is being passed to custom hook.
To correct this, pass the startDate and endDate directly (i.e. without toDate) to custom hook and manage the moment to date conversion in the custom hook.

React Datepicker( can't get value of input)

I am new in react. I need use react-datepicker
I want to get value of input, when I change date.
If i click on 20th October 2017, i want put 20th October 2017 in my variable.
But the main problem that I should work with component, not with input.
Before I just took value from state. Like this.state.value. But right now it is object(Moment) in state. And this object doesn't have value field.
There is my code:
export default class DatePicker extends Component {
constructor (props) {
super(props);
// this.props.date should looks like "29 November 2017 00:00"
// second argument for moment() it is format of date, because RFC 2822 date time format
this.state = {
date: moment(this.props.value, 'LLL')
};
}
handleChange = (date) => {
// const valueOfInput = this.state.date <--- I want string with date here
console.log('this.state.date',this.state.date);
this.setState({date: date});
};
render() {
return <Form.Field>
<Label>
<Picker
selected={this.state.date}
onChange={this.handleChange}
dateFormat="LLL"
locale={this.props.language}
/>
</Label>
</Form.Field>
Just use this:
handleChange = date => {
const valueOfInput = date.format();
///...
};
Because this datepicker returns a moment.js object!
For more information, look into the moment.js docs here.
Try this
<DatePicker
onChange={(value, e) => this.handleChange(value, e)}
selected={this.state.inputValue} otherProps={here}
/>
// you can access the value field in handleChange by e.target.value
handleChange(value, e) {
console.log(value); // this will be a moment date object
console.log(e.target.value); // this will be a string value in datepicker input field
}
This solved for me by using the following:
handleDateChange = date => {
let selectedDateFromCalender = date.toUTCString();
this.setState({
actualStartDate: selectedDateFromCalender,
});}
<DatePicker
selected={this.state.startDate}
onChange={this.handleDateChange}/>
You can use the following methods as well, choose according to your requirement:
toISOString: "2020-10-05T09:10:38.000Z"
toJSON: "2020-10-06T09:09:16.000Z"
toUTCString: "Thu, 08 Oct 2020 09:11:24 GMT"
If you want to get the new value (once the user clicks on a new date from DatePicker) pass the event to the method.
class MyComponent extends React.Component {
constructor(props) {
this.state = {inputValue: moment(),} // this will set the initial date to "today",
// you could also put another prop.state value there
this.handleChange = this.handleChange.bind(this);
}
}
handleChange(value) {
console.log(value); // this will be a moment date object
// now you can put this value into state
this.setState({inputValue: value});
}
render(){
...
<DatePicker
onChange={(event) => this.handleChange(event)}
selected={this.state.inputValue} otherProps={here} />
...
}
};
The new version of react-datepicker library stopped using a moment.js object, so here is my solution if you want to get a formatted string representation of the date.
First import
import format from "date-fns/format";
Then
<DatePicker
onChange={(value)=>this.handleChange(format(value, "yyyy/MM/dd", {
awareOfUnicodeTokens: true }))}
dateFormat="yyyy/MM/dd"
selected={this.state.inputValue} otherProps={here} />
...
You can use the getTime() helper function on your date object to get the millisecond timestamp for that specific date, which is a JavaScript number data type. Useful for saving data in the backend of your application. For example Flask Peewee ORM requires a timestamp to be saved for the DateTimeField.
const [startDate, setStartDate] = useState(new Date())
<DatePicker
onSelect( date => {
setStartDate(getTime(date))
}
/>
source: https://date-fns.org/v2.0.0-alpha.7/docs/getTime
Combine it with an effect to set the default state value to a timestamp on page load:
useEffect(() => {
setStartDate(getTime(startDate))
}, [])
Otherwise for your UI, you can use the format() helper function to get the string value of the given object, with any given format:
<h1>{format(startDate, "MMMM d, yyyy h:mm aa")}</h1>
source: https://date-fns.org/v2.0.0-alpha.7/docs/format
I have same problem, and I solved it by using the below solution. Please try it:
<p>{this.state.date.toLocaleDateString()}</p>
<input id="tdate" type="date" name="todate" onchange="getToDate(event);">
function getToDate(e){
const tdate = e.target.value;
//alert(tdate);
//console.log(tdate);
//return tdate;
};
here i am trying to access "tdate" variable outside the function.

Resources