Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use startOfDay from date-fns with timezones?

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

like image 769
Andrey Sylka Avatar asked Jan 26 '26 16:01

Andrey Sylka


1 Answers

Note: 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.

like image 68
D G Avatar answered Jan 29 '26 05:01

D G