I am working on displaying a banner on a website, between two dates (the campaign's start date and end date).
For this purpose, I have the following function:
function checkCampaignActive() {
const campaignDateStart = new Date('2022-09-14');
const campaignDateStop = new Date('2022-09-15');
const today = Date.now();
const isCampaignStarted = today >= campaignDateStart.getTime();
const isCampaignExpired = today > campaignDateStop.getTime();
this.isCampaignActive = isCampaignStarted && !isCampaignExpired;
console.log('Is started: ', isCampaignStarted);
console.log('Is expired: ', isCampaignExpired);
console.log(isCampaignActive);
}
checkCampaignActive();
The problem
Since getTime() returns the milliseconds between Jan 1 1970, 00:00 GMT and 2022-09-14 and 00:00 GMT and my local time is not GMT, there is a gap between the desired campaign start and the real one.
Whatever new Date('2022-09-14') returns has to be adjusted for the local time, but remain an object, not a string.
What is the easiest way to fix this issue?
The cause of the "problem" is that the date string your code provides to the Date constructor is interpreted as a simplified ISO 8601 format, denoting UTC.
The best way is to avoid a string typed argument, and provide numeric arguments. The equivalent of your example would be:
const campaignDateStart = new Date(2022, 8, 14);
const campaignDateStop = new Date(2022, 8, 15);
(Be aware of the zero-based month numbering)
It is possible to get away with it using strings. For instance, these options work because they step away from the format that JavaScript will interpret as UTC:
Append a time specification (without appending "Z" or other time zone specifier);
const campaignDateStart = new Date('2022-09-14 00:00');
const campaignDateStop = new Date('2022-09-15 00:00');
As stated by Mozilla Contributors:
Date-only strings (e.g. "1970-01-01") are treated as UTC, while date-time strings (e.g. "1970-01-01T12:00") are treated as local.
Or use a different delimiter:
const campaignDateStart = new Date('2022/09/14');
const campaignDateStop = new Date('2022/09/15');
But these rely on the complex rules of how strings are parsed as dates. Some of that logic is host-dependent, so in general it is better to avoid passing strings to the date constructor (or to Date.parse which uses the same logic for string arguments).
A small change to your code will make it work the way you wish.
When you call new Date('2022-09-14') it returns the number of milliseconds from 1970-01-01 00:00:00 UTC to 2022-09-14 00:00:00 UTC, however you actually with to get the number of milliseconds from 1970-01-01 00:00:00 UTC
2022-09-14 00:00:00 [local timezone].
It's easy to make this change, simply parse '2022-09-14 00:00:00' instead, and the same with the subsequent date. This will return the expected values and give the correct answer.
function checkCampaignActive() {
const campaignDateStart = new Date('2022-09-14 00:00');
const campaignDateStop = new Date('2022-09-15 00:00');
const today = Date.now();
const isCampaignStarted = today >= campaignDateStart.getTime();
const isCampaignExpired = today > campaignDateStop.getTime();
this.isCampaignActive = isCampaignStarted && !isCampaignExpired;
console.log('Is started: ', isCampaignStarted);
console.log('Is expired: ', isCampaignExpired);
console.log(isCampaignActive);
}
checkCampaignActive();
.as-console-wrapper { max-height: 100% !important; }
Visit this link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset
Add GMT in Date to add your timezone. Then use .getTimezoneOffset to get gap in Minutes.
function checkCampaignActive() {
const campaignDateStart = new Date('2022-09-14 GMT+5:45');
const campaignDateStop = new Date('2022-09-15 GMT+5:45');
const today = Date.now();
const today1 = Date.now() + campaignDateStart.getTimezoneOffset() * 60;
const isCampaignStarted = today >= campaignDateStart.getTime();
const isCampaignExpired = today > campaignDateStop.getTime();
this.isCampaignActive = isCampaignStarted && !isCampaignExpired;
console.log('Is started: ', isCampaignStarted);
console.log('Is expired: ', isCampaignExpired);
console.log(isCampaignActive);
console.log(today);
console.log(today1);
console.log(campaignDateStart.getTimezoneOffset());
}
checkCampaignActive();
Related
I got the following string: "2022/05/01 03:10:00" and I need to create a Date object forcing it to use Chile's UTC offset.
The problem is that because of Daylight saving time (DST) the offset changes twice a year.
How can get that Date object, for example, using the "America/Santiago" TZ db name?
Something like:
new Date("2022/05/01 03:10:00" + getUtcOffset("America/Santiago")).
function getUtcOffset(tzDbName) {
..
}
Returns -3 or -4, depending the time in the year.
EDIT:
I ended using a nice trick for determining if DST was on or off.
reference
const dst = hasDST(new Date(strDate));
function hasDST(date = new Date()) {
const january = new Date(date.getFullYear(), 0, 1).getTimezoneOffset();
const july = new Date(date.getFullYear(), 6, 1).getTimezoneOffset();
return Math.max(january, july) !== date.getTimezoneOffset();
}
Then I could create the date with the correct timezone depending on that variable.
if (dst) {
let d = new Date(strDate + " GMT-0300");
return d;
} else {
let d = new Date(strDate + " GMT-0400");
return d;
}
Thanks everyone!
EDIT2:
I finally found a very nice library that does exactly what I was looking for:
https://date-fns.org/v2.28.0/docs/Time-Zones#date-fns-tz
const { zonedTimeToUtc, utcToZonedTime, format } = require('date-fns-tz')
const utcDate = zonedTimeToUtc('2022-05-05 18:05', 'America/Santiago')
This has been discussed before here.
Haven't tested it, but it appears that the simplest solution is:
// Example for Indian time
let indianTime = new Date().toLocaleTimeString("en-US",
{timeZone:'Asia/Kolkata',timestyle:'full',hourCycle:'h24'})
console.log(indianTime)
You can check the link for more complex answers and libraries
Generals notes
To get the time zone name use:
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)
To get the difference from UTC (in minutes) use:
var offset = new Date().getTimezoneOffset();
console.log(offset);
// if offset equals -60 then the time zone offset is UTC+01
I have the following function which I've written to convert msSinceEpoch to the New Zealand Date (IE11 Compatible)...
const MAGICNUMBER = 13;
const toNewZealand = (msSinceEpoch) => {
const [day, month, year, time] = new Date(msSinceEpoch).toLocaleString("en-NZ", {
hour12: false, timeZone: "UTC"
}).split(/[/,]/); // timeZone UTC is the only format support on IE11
const [hours, minutes, seconds] = time.trim().split(":");
return new Date(~~year, ~~month - 1, ~~day, ~~hours + MAGICNUMBER, ~~minutes, ~~seconds)
};
// usage....
console.log(
toNewZealand(new Date().getTime())
)
However, this contains a magic number which is not relative to New Zealand's daylight savings time (+12 or +13).
So here it gets complicated, how do I get the right number relative to daylight savings in New Zealand (+12 or +13).
My initial attempt was just to calculate whether it was in between the last Sunday of September or first Sunday of April but then I realised that the second I use a new Date() constructor anywhere in the code it's going to create a date relative to their system time and break the math.
TL;DR Convert UTC Milliseconds since epoch to New Zealand Time that works with New Zealand's Daylight savings settings.
EDIT: Also not interested in using Moment or any other library to solve this problem due to bundle size costs.
TL;DR Convert UTC Milliseconds since epoch to New Zealand Time...
I think OP has a misunderstanding of what time conversion is. MS Since Epoch are always in UTC. Time conversation changes the display format of the time, and should not change msSinceEpoch
You can use toLocalString() and pass the desired timezone (Pacific/Auckland) to convert the display format of your DateTime:
const date = new Date();
console.log(
date.toLocaleString('en-NZ', {
timeZone: 'Pacific/Auckland',
}),
);
Solved it, tried to keep it as human readable as possible, basically tries to add 12 or 13 depending on where the current UTC DateTime lies, if by adding 12 or 13 we fall into the next daylight savings period we add the alternate instead....
IE if by adding 12 we fall into +13 territory.... add +13 instead.
IE if by adding +13 we fall into +12 territory.... add +12 instead.
New Zealand's daylight savings time changes on the last sunday of september and the first sunday of April.
This is the solution....
const UTCFromMS = (ms) => {
return new Date(new Date(ms).toUTCString().replace(" GMT", ""))
};
const addHours = (dte, hrs) => {
return new Date(
dte.getFullYear(),
dte.getMonth(),
dte.getDate(),
dte.getHours() + hrs,
dte.getMinutes(),
dte.getMilliseconds()
);
};
const toNewZealand = (ms) => {
return addNewZealandDaylightSavings(UTCFromMS(ms));
};
const getPreviousSunday = (dte) => {
return new Date(
dte.getFullYear(),
dte.getMonth(),
dte.getDate() - dte.getDay(),
1,
0,
0
);
};
const getNextSunday = (dte) => {
return new Date(
dte.getFullYear(),
dte.getMonth(),
dte.getDay() === 0 ? dte.getDate() : dte.getDate() + (7 - dte.getDay()),
1,
0,
0
)
};
const standardHours = 12;
const daylightHours = 13;
const addNewZealandDaylightSavings = (dte) => {
const lastSundaySeptember = getPreviousSunday(
new Date(dte.getFullYear(), 8, 30)
);
const firstSundayApril = getNextSunday(
new Date(dte.getFullYear(), 3, 1)
);
// If its before firstSundayApril, add 13, if we went over 1am, add 12.
if(dte <= firstSundayApril) {
const daylightNz = addHours(dte, daylightHours);
if(daylightNz >= firstSundayApril) {
return addHours(dte, standardHours);
}
return daylightNz
}
// if its before lastSundaySeptember, add 12 if we went over 1am add 13.
if(dte <= lastSundaySeptember) {
const standardNz = addHours(dte, standardHours);
if(standardNz >= lastSundaySeptember) {
return addHours(dte, daylightHours);
}
return standardNz;
}
return addHours(dte, daylightHours);
};
console.log(toNewZealand(new Date().getTime()).toString());
// the above line should always output the current DateTime in New Zealand, replace the argument with any epoch milliseconds and it should still always give you the correct time.
===EDIT====
The above answer has since become less relevant as now there's way to do this that are part of the web standard.
Date.prototype.toLocaleString AND timeZone: "Pacific/Auckland"
Is one of the simplest way's to convert dates today, you can read about it more on MDN here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
I found many solution that gives Timezone name from offset value. But I have Timezone name and I want offset value for that. I tried setTimezone('Asia/Kolkata'), but I think their is no method like setTimezone.
example:
Asia/Kolkata should give me -330 ( offset )
This has got the be the easiest way to accomplish this task with modern JavaScript.
Note: Keep in mind that the offset is dependent on whether Daylight Savings Time (DST) is active.
/* #return A timezone offset in minutes */
const getOffset = (timeZone = 'UTC', date = new Date()) => {
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
return (tzDate.getTime() - utcDate.getTime()) / 6e4;
}
console.log(`No arguments: ${getOffset()}`); // 0
{
console.log('! Test Case #1 >> Now');
console.log(`Asia/Colombo : ${getOffset('Asia/Colombo')}`); // 330
console.log(`America/New_York : ${getOffset('America/New_York')}`); // -240
}
{
console.log('! Test Case #2 >> DST : off');
const date = new Date(2021, 0, 1);
console.log(`Asia/Colombo : ${getOffset('Asia/Colombo', date)}`); // 330
console.log(`America/New_York : ${getOffset('America/New_York', date)}`); // -300
}
{
console.log('! Test Case #3 >> DST : on');
const date = new Date(2021, 5, 1);
console.log(`Asia/Colombo : ${getOffset('Asia/Colombo', date)}`); // 330
console.log(`America/New_York : ${getOffset('America/New_York', date)}`); // -240
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
I came across this same issue, and this is the solution I came up with, if you can get an IANA tz database name like the one you mentioned:
const myTimezoneName = "Asia/Colombo";
// Generating the formatted text
// Setting the timeZoneName to longOffset will convert PDT to GMT-07:00
const options = {timeZone: myTimezoneName, timeZoneName: "longOffset"};
const dateText = Intl.DateTimeFormat([], options).format(new Date);
// Scraping the numbers we want from the text
// The default value '+0' is needed when the timezone is missing the number part. Ex. Africa/Bamako --> GMT
let timezoneString = dateText.split(" ")[1].slice(3) || '+0';
// Getting the offset
let timezoneOffset = parseInt(timezoneString.split(':')[0])*60;
// Checking for a minutes offset and adding if appropriate
if (timezoneString.includes(":")) {
timezoneOffset = timezoneOffset + parseInt(timezoneString.split(':')[1]);
}
It's not a very nice solution, but it does the job without importing anything. It relies on the output format of the Intl.DateTimeFormat being consistent, which it should be, but that's a potential caveat.
You can't get it by name alone. You would also need to know the specific time. Asia/Kolkata may be fixed to a single offset, but many time zones alternate between standard time and daylight saving time, so you can't just get the offset, you can only get an offset.
For how to do it in JavaScript, see this answer.
Using countries and timezones npm package:
import {getTimezone} from 'countries-and-timezones';
const australianTimezone = 'Australia/Melbourne';
console.log(getTimezone(australianTimezone));
Prints to the console:
{
name: 'Australia/Melbourne',
country: 'AU',
utcOffset: 600,
utcOffsetStr: '+10:00',
dstOffset: 660,
dstOffsetStr: '+11:00',
aliasOf: null
}
From there, you can use the utcOffset or dstOffset depending on if it is daylight savings time.
To create Date object in UTC, we would write
new Date(Date.UTC(2012,02,30));
Without Date.UTC, it takes the locale and creates the Date object. If I have to create a Date object for CET running the program in some part of the world, how would I do it?
You don't create a JavaScript Date object "in" any specific timezone. JavaScript Date objects always work from a milliseconds-since-the-Epoch UTC value. They have methods that apply the local timezone offset and rules (getHours as opposed to getUTCHours), but only the local timezone. You can't set the timezone the Date object uses for its "local" methods.
What you're doing with Date.UTC (correctly, other than the leading 0 on 02) is just initializing the object with the appropriate milliseconds-since-the-Epoch value for that date/time (March 30th at midnight) in UTC, whereas new Date(2012, 2, 30) would have interpreted it as March 30th at midnight local time. There is no difference in the Date object other than the datetime it was initialized with.
If you need a timezone other than local, all you can do is use the UTC version of Date's functions and apply your own offset and rules for the timezone you want to use, which is non-trivial. (The offset is trivial; the rules tend not to be.)
If you go looking, you can find Node modules that handle timezones for you. A quick search for "node timezone" just now gave me timezone as the first hit. It also gave me links to this SO question, this SO question, and this list of timezone modules for Node.
function getCETorCESTDate() {
var localDate = new Date();
var utcOffset = localDate.getTimezoneOffset();
var cetOffset = utcOffset + 60;
var cestOffset = utcOffset + 120;
var cetOffsetInMilliseconds = cetOffset * 60 * 1000;
var cestOffsetInMilliseconds = cestOffset * 60 * 1000;
var cestDateStart = new Date();
var cestDateFinish = new Date();
var localDateTime = localDate.getTime();
var cestDateStartTime;
var cestDateFinishTime;
var result;
cestDateStart.setTime(Date.parse('29 March ' + localDate.getFullYear() + ' 02:00:00 GMT+0100'));
cestDateFinish.setTime(Date.parse('25 October ' + localDate.getFullYear() + ' 03:00:00 GMT+0200'));
cestDateStartTime = cestDateStart.getTime();
cestDateFinishTime = cestDateFinish.getTime();
if(localDateTime >= cestDateStartTime && localDateTime <= cestDateFinishTime) {
result = new Date(localDateTime + cestOffsetInMilliseconds);
} else {
result = new Date(localDateTime + cetOffsetInMilliseconds);
}
return result;
}
I am struggling to find out the beginning of day factoring in timezones in javascript. Consider the following:
var raw_time = new Date(this.created_at);
var offset_time = new Date(raw_hour.getTime() + time_zone_offset_in_ms);
// This resets timezone to server timezone
var offset_day = new Date(offset_time.setHours(0,0,0,0))
// always returns 2011-12-08 05:00:00 UTC, no matter what the offset was!
// This has the same issue:
var another_approach_offset_day = new Date(offset_time.getFullYear(),offset_time.getMonth(),offset_time.getHours())
I expect when i pass a Pacific Timezone offset, to get: 2011-12-08 08:00:00 UTC and so on.
What is the correct way to achieve this?
I think that part of the issue is that setHours method sets the hour (from 0 to 23), according to local time.
Also note that I am using javascript embedded in mongo, so I am unable to use any additional libraries.
Thanks!
Jeez, so this was really hard for me, but here is the final solution that I came up with the following solution. The trick was I need to use setHours or SetUTCHours to get the beginning of a day -- the only choices I have are system time and UTC. So I get the beginning of a UTC day, then add back the offset!
// Goal is given a time and a timezone, find the beginning of day
function(timestamp,selected_timezone_offset) {
var raw_time = new Date(timestamp)
var offset_time = new Date(raw_time.getTime() + selected_timezone_offset);
offset_time.setUTCHours(0,0,0,0);
var beginning_of_day = new Date(offset_time.getTime() - selected_timezone_offset);
return beginning_of_day;
}
In JavaScript all dates are stored as UTC. That is, the serial number returned by date.valueOf() is the number of milliseconds since 1970-01-01 00:00:00 UTC. But, when you examine a date via .toString() or .getHours(), etc., you get the value in local time. That is, the local time of the system running the script. You can get the value in UTC with methods like .toUTCString() or .getUTCHours(), etc.
So, you can't get a date in an arbitrary timezone, it's all UTC (or local). But, of course, you can get a string representation of a date in whatever timezone you like if you know the UTC offset. The easiest way would be to subtract the UTC offset from the date and call .getUTCHours() or .toUTCString() or whatever you need:
var d = new Date();
d.setMinutes(d.getMinutes() - 480); // get pacific standard time
d.toUTCString(); // returns "Fri, 9 Dec 2011 12:56:53 UTC"
Of course, you'll need to ignore that "UTC" at the end if you use .toUTCString(). You could just go:
d.toUTCString().replace(/UTC$/, "PST");
Edit: Don't worry about when timezones overlap date boundaries. If you pass setHours() a negative number, it will subtract those hours from midnight yesterday. Eg:
var d = new Date(2011, 11, 10, 15); // d represents Dec 10, 2011 at 3pm local time
d.setHours(-1); // d represents Dec 9, 2011 at 11pm local time
d.setHours(-24); // d represents Dec 8, 2011 at 12am local time
d.setHours(52); // d represents Dec 10, 2011 at 4am local time
Where does the time_zone_offset_in_ms variable you use come from? Perhaps it is unreliable, and you should be using Date's getTimezoneOffset() method. There is an example at the following URL:
http://www.w3schools.com/jsref/jsref_getTimezoneOffset.asp
If you know the date from a different date string you can do the following:
var currentDate = new Date(this.$picker.data('date'));
var today = new Date();
today.setHours(0, -currentDate.getTimezoneOffset(), 0, 0);
(based on the codebase for a project I did)
var aDate = new Date();
var startOfTheDay = new Date(aDate.getTime() - aDate.getTime() % 86400000)
Will create the beginning of the day, of the day in question
You can make use of Intl.DateTimeFormat. This is also how luxon handles timezones.
The code below can convert any date with any timezone to its beginging/end of the time.
const beginingOfDay = (options = {}) => {
const { date = new Date(), timeZone } = options;
const parts = Intl.DateTimeFormat("en-US", {
timeZone,
hourCycle: "h23",
hour: "numeric",
minute: "numeric",
second: "numeric",
}).formatToParts(date);
const hour = parseInt(parts.find((i) => i.type === "hour").value);
const minute = parseInt(parts.find((i) => i.type === "minute").value);
const second = parseInt(parts.find((i) => i.type === "second").value);
return new Date(
1000 *
Math.floor(
(date - hour * 3600000 - minute * 60000 - second * 1000) / 1000
)
);
};
const endOfDay = (...args) =>
new Date(beginingOfDay(...args).getTime() + 86399999);
const beginingOfYear = () => {};
console.log(beginingOfDay({ timeZone: "GMT" }));
console.log(endOfDay({ timeZone: "GMT" }));
console.log(beginingOfDay({ timeZone: "Asia/Tokyo" }));
console.log(endOfDay({ timeZone: "Asia/Tokyo" }));