In case I have a Date and I want to check if the time is DST I can use a method, such as the following:
function isDST(d) {
let jan = new Date(d.getFullYear(), 0, 1).getTimezoneOffset();
let jul = new Date(d.getFullYear(), 6, 1).getTimezoneOffset();
return Math.max(jan, jul) != d.getTimezoneOffset();
}
(source here)
In case I use MomentJS library I reach the same in this way:
moment().isDST();
Anyone knows how to do the same with the upcoming Temporal?
I'd recommend not doing this, at least not in this form.
Aside from the Ireland example mentioned in the comment, there are other time zone jurisdictions where there are one-time or non-semiannual changes in the offset from UTC that occurred for other reasons than DST, and any possible isDST() implementation will, by definition, malfunction in these cases. Another example is that Morocco observes year-round DST except during the month of Ramadan. For most of the world's population, "DST" has no meaning at all.
To solve this, I'd start by asking what you are going to use the information for?
If it's, for example, to specify "Daylight" or "Standard" time in the name of a time zone, you could instead use Intl.DateTimeFormat with the { timeZoneName: 'long' } option, which will give you the name of the time zone with this information included.
If you need it as a drop-in replacement for Moment's isDST() method so that you can port an existing system from Moment to Temporal, I'd recommend reimplementing the Moment function exactly, and plan to move away from the concept of "is DST" in the future. (Note, that the Moment documentation also describes this function as a hack that sometimes doesn't provide correct information.)
The body of the Moment function can be found here and the equivalent for Temporal would be:
function isDST(zdt) {
return (
zdt.offsetNanoseconds > zdt.with({ month: 1 }).offsetNanoseconds ||
zdt.offsetNanoseconds > zst.with({ month: 6 }).offsetNanoseconds
);
}
Another thing that you might need this information for is to interface with other systems that include an "is DST" bit in their data model (which is an incorrect concept, but you might have no choice.) In this case I'd recommend restricting the "is DST" function to a list of allowed time zones that are known to employ the concept of "DST" and returning false in other cases, which should at least filter out some of the false positives.
if (!listOfTimeZoneIDsWithDST.includes(zdt.timeZone.id))
return false;
the temporal api has a offsetNanoseconds read-only property
zdt = Temporal.ZonedDateTime.from('2020-11-01T01:30-07:00[America/Los_Angeles]');
zdt.offsetNanoseconds;
// => -25200000000000
also there's the with method which returns a new object with specified field being overwritten.
i have to admit i haven't tested it but something like this should basically be the equivalent to your function. (month index starts at 1)
function isDST(d) {
let jan = d.with({month: 1}).offsetNanoseconds ;
let jul = d.with({month: 7}).offsetNanoseconds ;
return Math.min(jan, jul) != d.offsetNanoseconds ;
}
zoned DateTime
refine dev
web dev simplified
Related
I've spent an hour looking for answers and trying different things so I appreciate any help here.
The following code works great for finding someone's part B effective date. However, when someone's birthday is really on the 1st of a month the 'if' function get's used, and I'm no longer able to format and write the date. It's almost like 'partB_eff' is no longer a date object. (I'm a newbie, so I might just be making this part up.)
I'm getting the error "TypeError: partB_eff.toLocaleDateString is not a function at AutoFill_6_Step_Checklist(Code:24:27)"
How can I resolve this?
let birthday = new Date(e.values[2]);
//this is a date entered from a google form
let bdayCopy = new Date(birthday);
//I created this since I'll be using .setMonth(), and I don't want to change the original date of the birhtday
let bday65 = new Date(bdayCopy.setMonth(bdayCopy.getMonth()+780));
//finds the 65th birthday
let partB_eff = new Date(bdayCopy.setDate(01));
//find's the Medicare part B effective date (the 1st of the month someone turns 65)
if(birthday.getDate()==1){
partB_eff = partB_eff.getMonth-1;
//if the person's birthday is really on the 1st of the month, the part b effective date is the 1st of the month prior. partB_eff must be converted
}
partB_eff = partB_eff.toLocaleDateString('en-us',{year:"numeric",month: "short",day:"numeric"});
//format partB_eff so that it looks nice on paper
partB_eff = partB_eff.getMonth-1;
Doesn't do what you think it does. What it does is get the vound function getDate from your date object, and attempt to subtract one from it. In any other language trying to do subtraction on a function would be a type error, but Javascript is Javascript and allows numeric operations on almost any type. A function minus a number in JS is NaN. NaN doesn't have a method called toLocaleString, hence the error.
What's interesting is that you did the same operation correctly above with bdayCopy.setMonth(bdayCopy.getMonth()+780)
Just do the same thing here
bdayCopy = new Date(bdayCopy.setMonth(bdayCopy.getMonth()-1));
Also some important concepts. if in Javascript is not a function. if is a keyword that starts a conditional statement. You can't do any of the things you can do with a function with if. You can't call it or assign it to a variable or pass ot as a function argument. Clearly understanding what a function is is something you need to do to be able to work in JS, or frankly any other language.
Finally if you are doing date math in JS I strongly recommend you use a date library like DateFns or Moment. Javascript native date APIs are possibly the worst designed date API of any language ever.
I want to test the following piece of code. I am wondering if there is a way to mock moment.js or force it to think my current location is America/New_York so that my unit test doesn't fail in gitlab.ci runner which may be in various geographical locations?
const centralTimeStartOfDay = moment.tz('America/Chicago').startOf('day');
const startHour = centralTimeStartOfDay
.hour(7)
.local()
.hour();
Basically I want to hard code my timezone to be America/New_York and want this function to behave consistently.
Edit:
I tried:
Date.now = () => new Date("2020-06-21T12:21:27-04:00")
moment.tz.setDefault('America/New_York')
And still, I get the same result. I want to mock the current time so startHour returns a consistent value.
The problem
So there is no one line answer to this question. The problem is a fundamental one to javascript, where you can see dates in one of two ways:
UTC (getUTCHours(), getUTCMinutes() etc.)
local (i.e. system, getHours(), getMinutes() etc.)
And there is no specified way to set the effective system timezone, or even the UTC offset for that matter.
(Scan through the mdn Date reference or checkout the spec to get a feeling for just how unhelpful this all is.)
"But wait!" we cry, "isn't that why moment-timezone exists??"
Not exactly. moment and moment-timezone give much better / easier control over managing times in javascript, but even they have no way to know what the local timezone Date is using, and use other mechanisms to learn that. And this is a problem as follows.
Once you've got your head round the code you'll see that the moment .local() method (prototype declaration and implementation of setOffsetToLocal) of moment effectively does the following:
sets the UTC offset of the moment to 0
disables "UTC mode" by setting _isUTC to false.
The effect of disabling "UTC mode" is to mean that the majority of accessor methods are forwarded to the underlying Date object. E.g. .hours() eventually calls moment/get-set.js get() which looks like this:
export function get(mom, unit) {
return mom.isValid()
? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
: NaN;
}
_d is the Date object that the moment (mom) is wrapping. So effectively for a non-UTC mode moment, moment.hours() is a passthrough to Date.prototype.getHours(). It doesn't matter what you've set with moment.tz.setDefault(), or if you've overridden Date.now(). Neither of those things are used.
Another thing...
You said:
Basically I want to hard code my time to be America/New_York and want this function behaves consistently
But actually, that is not generally possible. You are using Chicago, which I imagine has offset shifts in sync with New York, but e.g. the UK shifts at a different time from the US, so there are going to be weeks in the year where your test would fail if you were converting from a US timezone to a UK timezone.
The solutions.
But this is still frustrating, because I don't want my devs in Poland and the west coast of America to have breaking local tests because my CI server is running in UTC. So what can we do about it?
The first solution is a not-a-solution: find a different way of doing the thing you're doing! Generally the use cases for using .local() are quite limited, and are to display to a user the time in their current offset. It's not even their timezone because the local Date methods will only look at the current offset. So most of the time you'd only want to use it for the current time, or if you don't mind if it's wrong for half of the Date objects you use it for (for timezones using daylight savings). It could well be better to learn the timezone the user wants through other means, and not use .local() at all.
The second solution is also a not-a-solution: don't worry about your tests so much! The main thing with displaying a local time is that it works, you don't really care what it is exactly. Verify manually that it's displaying the correct time, and in your tests just verify that it returns a reasonable looking thing, without checking the specific time.
If you still want to proceed, this last solution at least makes your case work and a few others, and it's obvious what you need to do if you find you need to extend it. However, it's a complicated area and I make no guarantees that this will not have some unintended side-effects!
In your test setup file:
[
'Date',
'Day',
'FullYear',
'Hours',
'Minutes',
'Month',
'Seconds',
].forEach(
(prop) => {
Date.prototype[`get${prop}`] = function () {
return new Date(
this.getTime()
+ moment(this.getTime()).utcOffset() * 60000
)[`getUTC${prop}`]();
};
}
);
You should now be able to use moment.tz.setDefault() and using .local() should allow you to access the properties of the datetime as though it thought the local timezone was as configured in moment-timezone.
I thought about trying to patch moment instead, but it is a much more complicated beast than Date, and patching Date should be robust since it is the primitive.
try
// package.json
{
"scripts": {
"test": "TZ=EST jest"
}
}
Brilliant daphtdazz - thank you! To clarify for those who follow, this is the full solution I used to control the current date, timezone, and with daphtdazz's help - the local() behavior in moment:
import MockDate from 'mockdate';
const date = new Date('2000-01-01T02:00:00.000+02:00');
MockDate.set(date);
[
'Date',
'Day',
'FullYear',
'Hours',
'Minutes',
'Month',
'Seconds',
].forEach(
(prop) => {
Date.prototype[`get${prop}`] = function () {
return new Date(
this.getTime()
+ moment(this.getTime()).utcOffset() * 60000
)[`getUTC${prop}`]();
};
}
);
const moment = require.requireActual('moment-timezone');
jest.doMock('moment', () => {
moment.tz.setDefault('Africa/Maputo');
return moment;
});
I'd like to set a default time for momentjs. For instance the default behavior is:
moment().format('YYYY-MM-DD') // returns current date
What I'd like to do is to override the current date to some other date, i.e. 2017-03-01, so whenever I do
moment().format('YYYY-MM-DD')
>> "2017-07-31"
The Moment.js code calls new Date() to initialize itself when the constructor is called without arguments (technically, it calls new Date(Date.now()), but the result is the same). You have to pass something to get a specific date.
Of course, you could alter your local copy of the Moment.js library, but this is not recommended. You would have to keep it up-to-date with later releases of the libraries. And causing moment() to return anything other than the current date would cause those looking back at your code to wonder what's going on.
Upon further investigation, it seems Moment.js does allow you to overwrite the implementation of moment.now() which tells the rest of the library what time it is. See this article on the Moment.js website for more. There's an example there:
moment.now = function () {
return +new Date();
}
Which would be easy to alter for your needs:
moment.now = function () {
return +new Date(2017, 2, 1); // March 1st, 2017
}
I would strongly suggest using this technique sparingly (if at all) for the reasons given in the second paragraph above.
I'm sure this is a simple question, but I can't for the life of me solve it.
I have a JSON object as so:
{
"_id": {
"$oid": "57cb5aac9bd9a31100c793d1"
},
"reminders": [
"2014-03-12T12:00:00",
"2014-03-12T13:37:27",
"2014-03-12T13:37:27",
"2014-03-12T22:14:27"
],
"user": "xxx"
}
I want to parse the date from the reminders is JS to a date object in a loop, as so.
for (var i = userSchedule.reminders.length - 1; i >= userSchedule.reminders.length - 1; i++)
{
var date = new Date(userSchedule.reminders[i]);
}
But it just displays invalid date whenever I log it. Any ideas?
Though its not answer but why have you used user_schedule.reminders and userSchedule.reminders and your for loop will loop only once with correct data since your loop begins with i=3; which is index for last element of userSchedule.reminders[3] and when you loop next it will go beyond the scope of your array reminders
Something here is not as it seems, because calling the date constructor in both Chrome and Node.JS returns the correct date for me. I also tried it in the JSBin below.
https://jsbin.com/fomagugiwe/edit?html,output
I would log the value going into the date constructor, just to ensure that the value being used is of the correct format. Could you also provide the Node version you are using for this script for further testing..
for date time manipulation I strongly recommend using http://momentjs.com
where you can do
moment("your date string")
Or
moment("your date string","your date format")
Your loop is broken, it seems you're trying to iterate from 0 to userSchedule.reminders.length - 1, so:
for (var i=0; i < userSchedule.reminders.length; i++) {
// do stuff
}
Also, parsing date strings with the Date constructor (and Date.parse, they are equivalent for parsing) is not recommended due to variances in implementations. You should parse the string manually, a library can help but isn't necessary if you have only one format to parse.
A date string like "2014-03-12T12:00:00" should be treated as "local" (i.e. the host time zone offset should be used when calculating the time value), however not all implementations will do that. A small library like fecha.js makes it simple:
var d = fecha.parse('2014-03-12T12:00:00','YYYY-MM-DDTHH-mm-ss');
You can also use moment.js, but it's likely overkill for what you need. There are many other parsing and formatting libraries available that are suitable too.
What I'm looking for is a way to detect the browser's timezone ID (as defined in the Olson tables) but I don't care for the exact ID, I just need the ID of a timezone that works the same as the user's one (for example "Europe/Rome" is fine if the user is in Paris).
I'm not interested in the current offset, I really need the timezone so that I can send it to the server to do computations for other dates (the server has the Olson tables too).
Theoretically, as I already use Moment.js timezone library and have included the Olson tables, I don't need anything else, but I don't find any API to do the detection. I don't know if it's hidden somewhere or if somebody has it already written. One of the problems is that the current timezone plugin seems to keep its data private.
I dont' want a solution based on the integration of yet another copy or extract of the Olson tables (which I would have to maintain), I know there are a few libraries duplicating them, I want to use the ones I already have in Moment.js.
I made a small script to do that detection. It starts by registering the ids of the available timezones, then, on a call to the matches function tests all timezone ids for the current time and the times 4 and 8 months later (to filter out the timezones with different daylight rules) and five years before.
Here it is :
<script src="moment-with-langs.min.js"></script>
<script src="moment-timezone.min.js"></script>
<script src="moment-timezone-data.js"></script>
<script>
var tzdetect = {
names: moment.tz.names(),
matches: function(base){
var results = [], now = Date.now(), makekey = function(id){
return [0, 4, 8, -5*12, 4-5*12, 8-5*12, 4-2*12, 8-2*12].map(function(months){
var m = moment(now + months*30*24*60*60*1000);
if (id) m.tz(id);
return m.format("DDHHmm");
}).join(' ');
}, lockey = makekey(base);
tzdetect.names.forEach(function(id){
if (makekey(id)===lockey) results.push(id);
});
return results;
}
};
</script>
If you just want one timezone id, simply use
var tzid = tzdetect.matches()[0];
Demonstration
GitHub Repository : https://github.com/Canop/tzdetect.js
Update : The code here shown isn't compatible with the most recent versions of moment.js. Please refer to the GitHub repository for a maintained (free to use) code.
2017 Update: There's now an API in moment.js to guess the timezone. That's probably the best solution right now.
If you want to use the standard JavaScript API, you can use Intl.DateTimeFormat.resolvedOptions where there is browser support:
Intl.DateTimeFormat().resolvedOptions().timeZone; // "America/Los_Angeles"
resolvedOptions is currently (Jan 2016) available in all browsers except Safari on iOS and desktop: http://caniuse.com/#feat=internationalization
However, the timeZone property is currently only available on Chrome.
moment now has the guess() API as described here
There's a javascript tool that does just that :
https://github.com/entraigas/jstz
It seems to deal with timezones ambiguity also.
Combined with momentJS timezone, you can get the timezone and show formatted date :
var tzObj = jstz.determine();
var timezoneCode = tzObj.name();
console.log(moment.tz(timeZoneCode).format());