How to use startOfDay from date-fns with timezones? - javascript

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

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.

Related

Setting Time input format in Frontend

I'm trying to modify the below code in React Typescript. I want to return the input value in time format like - "Thu Jan 01 2022 13:03:00 GMT-0500 (Eastern Standard Time)" any suggestions how to do it?
Full Code: https://codepen.io/dcode-software/pen/jOwVqGO
function getTimeStringFromPicker(timePicker) {
const selects = getSelectsFromPicker(timePicker);
return `${selects.hour.value}:${selects.minute.value} ${selects.meridiem.value}`;
}
function numberToOption(number) {
const padded = number.toString().padStart(2, "0");
return `<option value="${padded}">${padded}</option>`;
}
activate();
You can create a new Date object and set the hours and minutes on it. From there you get convert it to a string. Like this:
function getTimeStringFromPicker(timePicker) {
const selects = getSelectsFromPicker(timePicker);
const d = new Date();
d.setMinutes(selects.minute.value);
// setHours takes in hours in 24hr format
if (selects.meridiem.value === "pm") {
d.setHours(selects.hour.value + 12);
} else {
d.setHours(selects.hour.value);
}
return d.toString();
}
If you can reach a Date object somehow, its toLocaleString() method can do something like that (the actual parameters are described here.
let date = new Date();
console.log(date.toLocaleString("en-US", {
timeZone: "EST",
dateStyle: 'full',
timeStyle: 'full',
hour12: false // this is because you seem to want a 24-hour format
}));
If you need more, moment.js might be a library to check.

CYPRESS: How to add one month to my current date with consideration to months like February(28 days) and months that have 30 days?

I have this cypress test where Im checking for a correct billing date. Our website has monthly subscriptions and it works as follows:
If you start your subscription on January 31st, your next billing date will automatically be on the 1st of March since February has only 28 days.
Same if you start your subscription on the 31st of March, then your next billing date will be on the first of 1st of May since there is no 31st in April and it automatically changes to the first day of the next month.
Starting on other normal dates like the 15th will always be the same date (15th) of the next month etc..
My issue is when testing this with cypress, i always get the last day of the next month. For example if i test that Im gonna start my subscription on the 31st of March, my test will have 30th of April as an expected result, which is not correct since i want the expected result of my test to be 1st of May.
I am using this function but i cant seem to make it work properly since there are many differences in the months.
export const getBillingDate = (todayDate: string, months: number) => {
const date1 = new Date(todayDate)
const date2 = new Date(todayDate)
date1.setDate(1)
const daysInNextMonth = getDaysInMonth(addMonths(date1, months))
date2.setDate(Math.min(date2.getDate(), daysInNextMonth))
return format(addMonths(date2, months), 'MMMM do, yyyy')
}
I would really appreciate anyone's help with this since i am new to Cypress and testing in general. (Sorry english is not my first language)
Both dayjs and javascript new Date() fail to add all the dates exactly as you want.
But you can use dayjs().daysInMonth() to get results exactly as per your description,
const getBilling = (startDate) => {
const [year, month, day] = startDate.split('/')
const sd = dayjs(startDate)
const firstOfNextMonth = sd.month(sd.month() + 1).date(1)
const daysInNextMonth = dayjs(firstOfNextMonth).daysInMonth()
let end;
if (daysInNextMonth < day) {
end = `${year}/${+month+2}/${1}` // always bump to 1st day of month + 2
} else {
end = `${year}/${+month+1}/${day}`
}
return dayjs(end, 'YYYY/MM/DD').format('YYYY/MM/DD')
}
it('gets billing date, accounting for short months', () => {
//Jan
expect(getBilling('2022/01/15')).to.eq('2022/02/15')
expect(getBilling('2022/01/31')).to.eq('2022/03/01')
//Feb
expect(getBilling('2022/02/15')).to.eq('2022/03/15')
expect(getBilling('2022/02/28')).to.eq('2022/03/28')
//Mar
expect(getBilling('2022/03/15')).to.eq('2022/04/15')
expect(getBilling('2022/03/31')).to.eq('2022/05/01')
})
Day.js already exists to do date math.
You can use their .add() to add 30 days to a date dayjs().add(30, 'day').
You can also format the dates with .format() to format the way you want it dayjs('2019-01-25').format('DD/MM/YYYY') // '25/01/2019'
Your requirements are a little unusual. Typically when adding a month and it overflows, the requirement is to return the last day of the month, not the first of the following month. But it's not difficult, just get the starting date (day in month), add a month, and if the resulting date isn't the same, set it to the 1st, e.g.:
function addBillingMonth(date = new Date()) {
let d = new Date(+date);
let dayNum = d.getDate();
d.setMonth(d.getMonth() + 1);
if (dayNum !== d.getDate()) {
d.setDate(1);
}
return d;
}
// Examples
[ new Date(2021,11,31), // 31 Dec
new Date(2022, 0,15), // 15 Jan
new Date(2022, 0,31), // 31 Jan
new Date(2022, 2,31), // 31 Mar
].forEach(d => console.log(d.toDateString() +
' next bill: ' + addBillingMonth(d).toDateString())
);
You have a months parameter, if you want to increase by more that one month you should calculate each month separately.
'dayjs` definitely gives you more options to play with.
const expect = chai.expect
const addBillingMonth = (start) => {
let next = start.add(1, 'month')
if (start.date() !== next.date()) {
next = next.add(1, 'month').startOf('month')
}
return next
}
const getBilling = (startDate, months = 1) => {
let result = dayjs(startDate)
for (let i = 0; i < months; i++) {
result = addBillingMonth(result) // repeat for each month
}
return result.format('YYYY/MM/DD')
}
expect(getBilling('2022/01/15')).to.eq('2022/02/15')
expect(getBilling('2022/01/31')).to.eq('2022/03/01')
expect(getBilling('2022/02/15')).to.eq('2022/03/15')
expect(getBilling('2022/02/28')).to.eq('2022/03/28')
expect(getBilling('2022/03/15')).to.eq('2022/04/15')
expect(getBilling('2022/03/31')).to.eq('2022/05/01')
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.6/chai.min.js"></script>
<script src="https://unpkg.com/dayjs#1.8.21/dayjs.min.js"></script>

Javascript find time difference in hours with timezone

I have a javascript object with the following details:
var dateobj = {
date: "2020-12-21 03:31:06.000000",
timezone: "Africa/Abidjan",
timezone_type: 3
}
var date = new Date();
var options = {
timeZone: dateobj.timezone
};
var curr_date = date.toLocaleString('en-US', options)
console.log(curr_date)
//I want this
//diff = curr_date - dateobj.date
I want to find the time difference in hours with the current date-time of the same timezone. I know I can use toLocaleString() function to get the date-time string in a particular timezone, but how can I find the time difference? The above code gets the current date time in that timezone, how can I find the time difference in hours?
In general when working with dates in JS I usually use a library called date-fns (Date functions). It just makes dates and time a lot easier to manage. This is how you would get the time difference in hours with date-fns.
const { differenceInHours } = require("date-fns");
const { zonedTimeToUtc } = require("date-fns-tz");
const timeData1 = {date: "2020-12-21 03:31:06.000000", timezone: "Africa/Abidjan", timezone_type: 3};
const timeData2 = {date: "2020-12-21 03:31:06.000000", timezone: "America/Los_Angeles", timezone_type: 3};
const t1 = zonedTimeToUtc(timeData1.date, timeData1.timezone);
const t2 = zonedTimeToUtc(timeData2.date, timeData2.timezone);
const diff = differenceInHours(t2, t1);
console.log(diff);
// => 8
Run demo: https://runkit.com/embed/jtfu0ixxthy7

Get timestamp from date string without timezone and timezone name apart

I have a dateTime string in this (bad, I know) format coming from an external API:
const startDate = '2/13/2020 15:00';
and the timezone name:
const timezoneName = 'America/New_York';
Which means that the dateTime is 2/13/2020 15:00 in New York.
Any idea for an elegant way to get timestamp (or JavaScript date object)?
I don't mind using moment.js or/and moment-timezone if it helps.
Moment and Moment-Timezone are for legacy code.
For new applications, the Moment team recommends Luxon.
const startDate = '2/13/2020 15:00';
const timezoneName = 'America/New_York';
const dateTime = luxon.DateTime.fromFormat(startDate, 'M/d/yyyy HH:mm',
{ zone: timezoneName });
const utcDateTime = dateTime.toUTC();
const s = utcDateTime.toISO();
console.log(s); //=> "2020-02-13T20:00:00.000Z"
Using moment-timezone should work:
const moment = require('moment-timezone');
const t = moment.tz("2/13/2020 15:00", "MM/DD/YYYY HH:mm","America/New_York");
console.log(t.toISOString()); // Prints '2020-02-13T20:00:00.000Z'

Get utc offset from timezone in Javascript

I need a Javascript function that given a timezone, returns the current UTC offset.
For example, theFuncIneed('US/Eastern') -> 240
In general, this is not possible.
US/Eastern is an identifier for a time zone. (It's actually an alias to America/New_York, which is the real identifier.)
240 is a time zone offset. It's more commonly written as -04:00 (Invert the sign, divide by 60).
The US Eastern Time Zone is comprised of both Eastern Standard Time, which has the offset of -05:00 and Eastern Daylight Time, which has the offset of -04:00.
So it is not at all accurate to say US/Eastern = 240. Please read the timezone tag wiki, especially the section titled "Time Zone != Offset".
Now you did ask for the current offset, which is possible. If you supply a date+time reference, then you can resolve this.
For the local time zone of the computer where the javascript code is executing, this is built in with .getTimeZoneOffset() from any instance of a Date object.
But if you want it for a specific time zone, then you will need to use one of the libraries I listed here.
It has become possible nowaday with Intl API:
The implementation of Intl is based on icu4c. If you dig the source code, you'll find that timezone name differs per locale, for example:
for (const locale of ["ja", "en", "fr"]) {
const timeZoneName = Intl.DateTimeFormat(locale, {
timeZoneName: "short",
timeZone: "Asia/Tokyo",
})
.formatToParts()
.find((i) => i.type === "timeZoneName").value;
console.log(timeZoneName);
}
Fortunately, there is a locale, Interlingua (the langauge tag is ia), which uses the same pattern (ex. GMT+11:00) for timezone names.
The snippet below can meed your need:
const getOffset = (timeZone) => {
const timeZoneName = Intl.DateTimeFormat("ia", {
timeZoneName: "short",
timeZone,
})
.formatToParts()
.find((i) => i.type === "timeZoneName").value;
const offset = timeZoneName.slice(3);
if (!offset) return 0;
const matchData = offset.match(/([+-])(\d+)(?::(\d+))?/);
if (!matchData) throw `cannot parse timezone name: ${timeZoneName}`;
const [, sign, hour, minute] = matchData;
let result = parseInt(hour) * 60;
if (sign === "+") result *= -1;
if (minute) result += parseInt(minute);
return result;
};
console.log(getOffset("US/Eastern")); // 240
console.log(getOffset("Atlantic/Reykjavik")); // 0
console.log(getOffset("Asia/Tokyo")); // -540
This way can be a little tricky but it works well in my production project. I hope it helps you too :)
Update
Many thanks to Bort for pointing out the typo. I have corrected the snippet.
Following function can be used to return the UTC offset given a timezone:
const getTimezoneOffset = (timeZone, date = new Date()) => {
const tz = date.toLocaleString("en", {timeZone, timeStyle: "long"}).split(" ").slice(-1)[0];
const dateString = date.toString();
const offset = Date.parse(`${dateString} UTC`) - Date.parse(`${dateString} ${tz}`);
// return UTC offset in millis
return offset;
}
It can be used like:
const offset = getTimezoneOffset("Europe/London");
console.log(offset);
// expected output => 3600000
You can do this using moment.js
moment.tz('timezone name').utcOffset()
Although this involves using moment-timezone.js
The answer of #ranjan_purbey results in NaN for me and the answer of #Weihang Jian throws an exception in Chrome (but works in Firefox).
Therefore, based on all the answers I came up with the following function which is basically a combination of both answers working together successfully for me:
function getTimeZoneOffset(timeZone) {
const date = new Date().toLocaleString('en', {timeZone, timeZoneName: 'short'}).split(' ');
const timeZoneName = date[date.length - 1];
const offset = timeZoneName.slice(3);
if (!offset) {
return 0;
}
const matchData = offset.match(/([+-])(\d+)(?::(\d+))?/);
if (!matchData) {
throw new Error(`Cannot parse timezone name: ${timeZoneName}`);
}
const [, sign, hour, minute] = matchData;
let result = parseInt(hour, 10) * 60;
if (sign === '+') {
result *= -1;
}
if (minute) {
result += parseInt(minute, 10);
}
return result;
}

Categories

Resources