Get the week day from a given date - javascript

I am writing my own calendar plugin but I am struggling to get the day of the week for a given date.
Lets say I have the year - 1905, the month - 4 and the day - 23. Is there any way at all that I could use this data - smash it together in a better format say YYYY/DD/MM and use this to calculate the day of the week - I was hoping to find some Gregorian calendar methods like in PHP but I am yet to find any.
Sorry if this has been asked before but I could only find questions using new Date for todays day of the week.

Construct a date object and ask it what day it is:
// Note months are zero indexed so May is 4, not 5
var d = new Date(1905, 4, 23).getDay(); // 2
Which you can turn into a function:
function getDay(y, m, d) {
var days = ['Sunday','Monday','Tuesday',
'Wednesday','Thursday','Friday','Saturday'];
var d = new Date(y, --m, d);
return d && days[d.getDay()];
}
// What day was 23 May, 1905?
console.log(getDay(1905,5,23)); // Tuesday
Edit
Note that these types of functions will convert two digit years into 20th century dates, e.g.
var d = new Date(5, 4, 23) // 23 May, 1905
and they don't check that the values are valid dates:
var d = new Date(5, 16, 23) // 23 May, 1906
If you don't want that, you have to construct the date differently and check the values:
// Create a generic date object
var d = new Date();
// Set the values for 23 May, 0005
// This way doesn't convert 5 to 1905
d.setFullYear(5, 4, 23);
// Check at least 2 values in the date are the same as the input
// The easiest to check are year and date
if (d.getFullYear() == 5 && d.getDate() == 23) {
console.log(d + 'is a valid date!');
}
You can create a separate function based on the above to create dates that returns either a date object or NaN if the values aren't valid (and honours two digit years).

Related

new Date() behaving inconsistently in JavaScript

I have a JavaScript control consisting of 3 dropdowns to collect date of birth from users (don't ask me why we're not using a date picker)
A dropdown for day, month, and year. When all 3 dropdowns have a value selected, I collect the values and construct a date:
var d = new Date(Date.UTC(this.selectedYear, this.selectedMonth - 1, this.selectedDay));
I'm not sure why you need to subtract 1 from the month, I just copied that bit from existing codebase and it seems to work. I guess months have a zero-based index for whatever reason.
The obvious problem with these dropdowns is that the user can select an invalid date. There's nothing stopping them to select 31 for day and then select February. This will result in the date being shifted forward by a number of days, i.e. values 1999, 2, 31, would give me March 3rd, 1999. So I have a check in place:
if (d.getDate() != this.selectedDay) {
d = new Date(Date.UTC(this.selectedYear, this.selectedMonth - 1, 0));
this.selectedDay = d.getDate();
If the day of month is different then my original value, it means the date has shifted, so I change the date to the last day of last month (hence the 0 for day) and then I update the value in the 'days' dropdown.
The strange thing is, I can't do this.selectedMonth - 1 in this second conversion. This would bring me all the way back to January 31st. So I have to do the following:
var d = new Date(Date.UTC(this.selectedYear, this.selectedMonth - 1, this.selectedDay));
if (d.getDate() != this.selectedDay) {
d = new Date(Date.UTC(this.selectedYear, this.selectedMonth, 0));
this.selectedDay = d.getDate();
}
This works, but it's just mind-boggling to me. Why do months behave differently if I use 0 as a value for days instead of a valid day of month? Why would anyone implement it this way? Does anyone have a logical explanation?
Actually, it's not the month field that's any different from the others: it's the day part that is the only one that is 1-indexed, as you can find in the docs on Date:
Given at least a year and month, this form of Date() returns a Date object whose component values (year, month, day, hour, minute, second, and millisecond) all come from the following parameters. Any missing fields are given the lowest possible value (1 for the day and 0 for every other component).
(emphasis mine)
The incorrect assumption you made is that using 0 for the day part would represent the last day of the month. There's no such quirks to JS Dates!
Picking the day 0, instead of the day 1, merely offsets the Date you picked by one day. Just one full day before the first day of the month...
That would be the last day of the previous month.
If the month you want to select is this.selectedMonth - 1, its last day is obtained by doing the following:
select the following month — that's + 1, so: (this.selectedMonth - 1) + 1
go back one day before the first — that's day 0, one before the day 1.
This is exactly what you're doing when you go:
// ┌───── year ────┐ ┌───── month ────┐ day
new Date(Date.UTC(this.selectedYear, this.selectedMonth, 0))
// │ │ │
// ┌─────────────────────┘ │ │
// the month after (this.selectedMonth - 1) │
// │
// ┌──────────────────────────────┤
// the day before the first day (1)
Sometimes, even an ASCII picture is worth more than a thousand words 🙂.
As others have pointed out, months are zero indexed hence you need to subtract 1 from the calendar month number. You can make life easier by setting the values of the month options to their month index (0 to 11) rather than calendar month number (1 to 12). The displayed text can be the calendar month number or name.
To validate the input date values, you can just check that the month hasn't changed when creating the date.
I can't see why you're using Date.UTC, it will change the date for users east of Greenwich as for anyone with a positive timezone offset (or negative ECMAScript offset) 2019-10-01 UTC is 2019-09-30 local.
let i;
let ySel = document.getElementById('inYear');
for (i=0; i<10; i++) {
ySel.appendChild(new Option(2010+i, 2010+i));
}
let mSel = document.getElementById('inMonth');
'January February March April May June July August September October November December'
.split(' ')
.forEach((m, i) => mSel.appendChild(new Option(m, i)));
let dSel = document.getElementById('inDay');
for (i=1; i<32; i++) {
dSel.appendChild(new Option(i, i));
}
[ySel, mSel, dSel].forEach(el => el.addEventListener('change', validateDate, false));
function validateDate(){
let y = document.getElementById('inYear').value;
let m = document.getElementById('inMonth').value;
let d = document.getElementById('inDay').value;
let date = new Date(y, m, d); // m is already zero indexed
let errSpan = document.getElementById('dErr');
if (date.getMonth() != m) {
errSpan.textContent = 'Invalid date';
} else {
errSpan.textContent = date.toString();
}
}
Year: <select id="inYear"></select>
Month: <select id="inMonth"></select>
Day: <select id="inDay"></select><span id="dErr"></span>
I would suggest to make year input first and required, after it has value enable month picker and then create new date object from where you would display day picker

moment.js thinks that 2013-12-31 is week 1, not week 53

The moment.js library is awesome, and I use it almost all the time, but I recently ran into something interesting. I'm trying to plot data by week of the year, and one of the data points was 2013-12-31, moment.js tells me this is week 1? Is there a better way to handle this? either with 53, null, -1, or something?
moment('2013-12-31').week()
(returns) 1
I thought maybe using isoWeek or format would get around this, but they all return the same value of 1, despite the documentation saying it goes to 53.
moment('2013-12-31').isoWeek()
(returns) 1
+moment('2013-12-31').format('w')
(returns) 1
Anyone have any ideas on this? (short of making a test whether the week computed has a min/max date that covers the date value I passed it)
It is because the week from the 30th december 2013 is considered to be the 1st week of 2014 as you may see on this page epoch converter
And according to momentjs documentation:
The week with January 1st in it is the first week of the year.
I had a problem at my work where we used .format('YYYY WW') for some comparison logic.
That doesn't really make sense, as you should probably use .format('gggg WW') in such cases.
moment('2013-12-31').format('YYYY w'); // Returns 2013 1
moment('2013-12-31').format('gggg w'); // Returns 2014 1
https://momentjs.com/docs/#/displaying/format/
This is expected behavior. According to the ISO 8601 standard, 2013 is not a year with 53 weeks.
The long years, with 53 weeks in them, can be described by any of the following equivalent definitions:
any year starting on Thursday (dominical letter D or DC) and any leap year starting on Wednesday (ED)
any year ending on Thursday (D, ED) and any leap year ending on Friday (DC)
years in which 1 January and 31 December (in common years) or either (in leap years) are Thursdays
(source)
2013 started and ended on a Tuesday so therefore it is not a "long year" and 2013-12-31 is considered part of the first week of 2014.
If you want that week to be the 53rd, you'll have to write custom code for it as the ISO standard won't agree with you!
Moment.js docs aren't that straightforward with this I had to move from WW-YYYY to WW-GGGG
moment(2019-12-30T00:20:53.380Z).format(WW-YYYY) // Gave me 01-2019 incorrectly
moment(2019-12-30T00:20:53.380Z).format(WW-GGGG) // Gave me 01-2020 correctly
Findings
If your doing locale weeks, use ww & gggg
If your doing ISO weeks, use WW & GGGG
A mix of w/W & Y is incorrect usage
I had the same problem with the calculation of the week number, starting from the date of Sunday.
Finally I was able to solve the problem by calculating the week number starting not from Sunday but from Monday.
moment(date).isoWeekday(1).week()
Better right a custom method which will convert date into week and that can be customized easily.
//value : (MMM DD YYYY format)
function getEpiWeek(value) {
Date.prototype.getWeek = function () {
var target = new Date(this.valueOf());
// ISO week date weeks start on monday, so correct the day number
var dayNr = (this.getDay() + 7) % 7;
// Set the target to the thursday of this week so the
// target date is in the right year
target.setDate(target.getDate() - dayNr + 3);
// ISO 8601 states that week 1 is the week with january 4th in it
var jan4 = new Date(target.getFullYear(), 0, 4);
// Number of days between target date and january 4th
var dayDiff = (target - jan4) / 86400000;
if (new Date(target.getFullYear(), 0, 1).getDay() < 4) {
return 1 + Math.ceil(dayDiff / 7);
}
else { // jan 4th is on the next week (so next week is week 1)
return Math.ceil(dayDiff / 7);
}
};
var weekNumber = new Date(value).getWeek()
var year = getYear(value, weekNumber);
return weekNumber + ' ' + year;
}
function getYear(value, weekNumber) {
var year = parseInt(value.split(' ')[2]);
if (value.split(' ')[0] == 'Jan') {
if (weekNumber > 40) {
year = year - 1;
}
}
if (value.split(' ')[0] == 'Dec') {
if (weekNumber < 2) {
year = year + 1;
}
}
return year.toString();
}
Personally solved my ordering issue using :
if(d.month()==0) {
week = d.week();
}else{
week=d.isoWeek();
}

JS time calculation - How many times a date has occurred between two dates

I really need some assistance with a time calculation in JS.
Put basically I need to calculate how many times a day of a month has occurred between two dates.
For Example -
A date of 15th of the month between 1st February 2014 to 14 May 2014 would be 3
A date of 15th of the month between 1st February 2014 to 16 May 2014 would be 4
I've looked at moment Jquery library but it estimates that a month is 30 days so I wouldn't be exact and take into consideration leap years - months with 28 days etc..
It really needs to be exact because its for a chargeable event calculation. The dates can spare many years so could lead to in-accuries because of the 30 day thing.
Any help would be appreciated
There are probably a million ways to do this... here's a brute force way:
// add a "addDays() method to Date"
Date.prototype.addDays = function(days)
{
var dat = new Date(this.valueOf());
dat.setDate(dat.getDate() + days);
return dat;
}
// provide two dates and a day ordinal you want to count between the two
function numOrdinalsBetweenDts(Date1, Date2, theOrdinal) {
var temp;
if(Date2 < Date1) { // put dates in the right order (lesser first)
temp = Date1;
Date1 = Date2;
Date2 = temp;
}
var workDate = Date1;
var ctr = 0;
while(workDate < Date2) { // iterate through the calendar until we're past the end
if(workDate.getDate() == theOrdinal) // if we match the ordinal, count it
ctr++;
workDate = workDate.addDays(1); // move the calendar forward a day
}
return ctr;
}
var result = numOrdinalsBetweenDts(new Date("July 21, 1901"), new Date("July 21, 2014"), 2);
console.log(result);
alert(result);
There is a slightly counter-intuitive behavior in the Javascript Date constructor where if you create a new Date with the day set to 0, it will assume the last day of the month. You can the use the following function get the number of days in a month:
function daysInMonth(month, year) {
return new Date(year, month, 0).getDate();
}
The Javascript date object is leap-year aware, so you can use this function reliably.
You then just need to count the number of months between the start and end date and check each one to make sure the day number is actually present in the month. You can short-circuit this check if the day is less than or equal to 28.

Convert milliseconds to years

I have a validator that checks if an user is at least 18 years old.
This is the check:
var res = /^([1-2]\d{3})\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])\-([0-9]{4})$/.exec(str);
var todays_date = new Date();
var birth_date = null;
if (res != null) {
birth_date = new Date(res[1], res[2], res[3]);
if (todays_date - birth_date > 565633905872) {
565633905872 is 18 years in milliseconds but how do I convert it to years before so I can just do:
if (todays_date - birth_date => 18) {
The number you have quoted is not the number of milliseconds in 18 years. It's too small even if you pretend there are no leap years.
The simplest way to test if somebody is at least 18 years old is to initialise a date object to their birthday, then use .getFullYear() and .setFullYear() to directly set the year 18 years forward. Then compare that with the current date.
Note also that in JS dates the month is zero-based, so you probably want to use res[2] - 1 when creating the date object.
birth_date = new Date(res[1], res[2] - 1, res[3]);
birth_date.setFullYear(birth_date.getFullYear() + 18);
if (birth_date <= new Date()) {
Or given you are constructing the birth_date from individual year, month and day you could just do:
birthPlus18 = new Date(+res[1] + 18, res[2] - 1, res[3]);
if (birthPlus18 <= new Date()) {
(The leading + in +res[1] + 18 is not a typo, it converts the string extracted by your regex into a number so that you can add 18 to it. You don't need to do the same thing for res[2] - 1 because - automatically converts both operands.)
Note also that your regex will happily allow dates that specify a day that is too high for the month, e.g., Feb 30 or Jun 31.
There are better ways of checking this (see the answer of "nnnnnn"). But your question wasn't about a better way but, how you could convert to years.
You could write a function that does that, example:
function convertmili( mSeconds )
{
return mSeconds / 31536000000;
}
The output of this function is still far from ideal, because your example would output: 17.9361334941654
So we could clean it up a bit:
function convertmili( mSeconds )
{
var checkYear = Math.floor(mSeconds / 31536000000);
return checkYear;
}
With this function, your example would output 17 and then you can check it the way you wanted.
Divide your millisecond value by 31536000000 you get number of years
http://www.convertunits.com/from/milliseconds/to/year

Extract Values from incorrect Js Date Object?

I've build a function which checks if a date parts values are valid :
Bad value example :
new Date(2012,3,44) =
Mon May 14 2012 00:00:00 GMT+0300 (Jerusalem Daylight Time)
Here is the function ( its arguments are being sent by me separately !)
function isDate(year, month, day)
{
...
}
alert(isDate( 2001,2,29));
However , I have a problem.
If I have an invalid date object like : var t= new Date(2001,2,44) :
And I want to send it to my function , I need to extract its values.
How can I extract the 44 value + 2 value ?
t.getDate() //13
t.getMonth() //3 (days went from march to april)
any help ?
You can't extract the value 44 from the Date object, because it's not there.
When creating a Date object with out of range values, the values will be adjusted so that it becomes a valid date.
The components of a Date object always forms a valid date. If you want to check if the values are valid, you have to do that before creating the Date object, or use the Date object to check them:
var d = new Date(year, month, day);
if (d.getFullYear() == year && d.getMonth() == month && d.getDate() == day) {
// components were valid
}

Categories

Resources