I try to replace momentjs with date-fns
but I am struggling with a very simple thing: I need to calculate startOfDay, end of day and addDays for the dates that I have got in timestamps and according to the giving timezone.
date = 1492437600; // Monday April 17, 2017 12:00:00 (pm) in time zone America/Noronha
timeZone = 'America/Noronha';
_startOfDay = utcToZonedTime(startOfDay(date*1000), timeZone).getTime()/1000;
and I have got the result for the _startOfDay = 1492365600 that is Sunday April 16, 2017 16:00:00 (pm) in time zone America/Noronha
What I am doing wrong? thanks in advance
I try to replace momentjs with date-fns
but I am struggling with a very simple thing: I need to calculate startOfDay, end of day and addDays for the dates that I have got in timestamps and according to the giving timezone.
date = 1492437600; // Monday April 17, 2017 12:00:00 (pm) in time zone America/Noronha
timeZone = 'America/Noronha';
_startOfDay = utcToZonedTime(startOfDay(date*1000), timeZone).getTime()/1000;
and I have got the result for the _startOfDay = 1492365600 that is Sunday April 16, 2017 16:00:00 (pm) in time zone America/Noronha
What I am doing wrong? thanks in advance
Share Improve this question asked Jun 3, 2021 at 10:15 Andrey SylkaAndrey Sylka 1711 gold badge2 silver badges6 bronze badges 1- when you call your method startOfDay(..), you do not specify a timezone so the return date can be the start of Day in any timezone. My guess would be UTC but it could be another timezone. – Godev Commented Jun 3, 2021 at 10:30
4 Answers
Reset to default 14Note: See the bottom of this answer for the best solution
Old Answer:
I see this question is 11 months old (as of writing) but thought I'd answer it as I hit the same problem, and others may come here in the future with the same question.
The date-fns library doesn't have timezone support, so you need to also use the date-fns-tz library. Import the getTimezoneOffset function from date-fns-tz and use to calculate the offset between the local (browser) timezone and the timezone you wish to calculate end of day for (i.e. America/Noronha). Note that the date-fns "endOf" functions use the local/browser timezone.
E.g.
import { endOfDay } from 'date-fns';
import { getTimezoneOffset } from 'date-fns-tz/esm';
const MILLISECS_IN_DAY = 86400000;
const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; // Browser Timezone
const tzOffset = getTimezoneOffset('America/Noronha') - getTimezoneOffset(localTz);
// Have to cater for negative offsets
const tzOffsetEOD = (tzOffset < 0) ? (MILLISECS_IN_DAY + tzOffset) : tzOffset;
let testDate = new Date();
let eodInTimezone = endOfDay(testDate).getTime() - tzOffsetEOD; // in millisecs
// Have to project forward a day if the result is in the past
if (eodInTimezone < testDate.getTime()) eodInTimezone += MILLISECS_IN_DAY;
Someone may be able to come up with a more elegant solution to this problem. If I do I'll post back here.
New Answer:
This solution works best for all "End Of" date-fns functions:
import { endOfDay, endOfWeek, endOfMonth, endOfYear } from 'date-fns';
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz/esm';
const calcZonedDate = (date, tz, fn, options = null) => {
const inputZoned = utcToZonedTime(date, tz);
const fnZoned = (options) ? fn(inputZoned, options) : fn(inputZoned);
return zonedTimeToUtc(fnZoned, tz);
}
const getZonedEndOfDay = (date, timeZone) => {
return calcZonedDate(date, timeZone, endOfDay);
}
const getZonedEndOfWeek = (date, timeZone) => {
return calcZonedDate(date, timeZone, endOfWeek, { weekStartsOn: 1 });
}
const getZonedEndOfMonth = (date, timeZone) => {
return calcZonedDate(date, timeZone, endOfMonth);
}
const getZonedEndOfYear = (date, timeZone) => {
return calcZonedDate(date, timeZone, endOfYear);
}
// Example Usage
let endOfDayZoned = getZonedEndOfDay(new Date(), 'America/Noronha');
Hope this helps.
I would suggest to take a look at another date management library. date-fns seems to be designed to work in current timezone and does not provide right tools for timezone calculations.
The same thing in Luxon for example:
const startOfDay = DateTime
.fromMillis(1492437600000)
.setZone('America/Noronha')
.startOf('day')
Then you can use startOfDay.toJSDate()
to get Date object with right moment in time, or you could use startOfDay.toFormat('dd-MM-yyyy HH:mm:ss')
for text output.
Based on the great answer by @D G, here's a typed version:
type DateType = Date | number | string
function calcZonedDate<
T extends (date: DateType, options?: any) => Date,
O = T extends (date: DateType, options?: infer U) => Date ? U : undefined
>(date: DateType, tz: string, fn: T, options?: O): Date {
const inputZoned = toZonedTime(date, tz)
const fnZoned = fn(inputZoned, options)
return fromZonedTime(fnZoned, tz)
}
function zonedEndOfDay(date: DateType, tz: string) {
return calcZonedDate(date, tz, endOfDay)
}
function zonedEndOfWeek(date: DateType, tz: string, options?: EndOfWeekOptions) {
return calcZonedDate(date, tz, endOfWeek, options)
}
function zonedEndOfMonth(date: DateType, tz: string) {
return calcZonedDate(date, tz, endOfMonth)
}
function zonedEndOfYear(date: DateType, tz: string) {
return calcZonedDate(date, tz, endOfYear)
}
startOfDay is now a thing in DateFns. Example from https://date-fns.org/v2.16.1/docs/startOfDay
The start of a day for 2 September 2014 11:55:00:
var result = startOfDay(new Date(2014, 8, 2, 11, 55, 0))
//=> Tue Sep 02 2014 00:00:00