having trouble with luxon, calculating time difference between 2 dates - javascript

kind people of StackOverflow, I'm having a problem with luxon (I guess I set it up wrong or did something wrong with it), right now it can't calculate future dates (says NaN), and for past dates take years into account so you get something like this:
[
what I want this code to do is focus on month and day (forget about the year) and if the passed date is 7 days away or is less than 7 days away from today's date say "the passed date is <= a week", later this will trigger a notifier, that will notify me a week in advance of my friends & families bdays.
the code:
const isBirthdayThisWeek = (birthDate) => {
const endDate = DateTime.now()
const startDate = DateTime.fromISO(birthDate)
const interval = Interval.fromDateTimes(startDate, endDate)
const dateDifference = interval.length('days')
const wholeNumberedDateDifference = Math.round(dateDifference)
wholeNumberedDateDifference <= 7
? console.log('bday is in less than a week', wholeNumberedDateDifference)
: wholeNumberedDateDifference > 7
? console.log('bday is more than in a week', wholeNumberedDateDifference)
: console.log('something went wrong', wholeNumberedDateDifference)
}
Thank you all in advance.

You can use the magnitude of a compute duration. Durations work for both intervals into the future and past. Adjust the start date to "snap" to the same current year so the date diff doesn't cross year boundary.
import { DateTime } from "luxon";
const startDate = DateTime.fromISO("2000-04-26").set({
year: DateTime.now().get("year")
});
const diff = Math.abs(startDate.diffNow().as('day'));
const wholeNumberedDateDifference = Math.round(diff);
if (diff <= 7) {
console.log("bday is in less than a week", wholeNumberedDateDifference);
} else {
console.log("bday is more than in a week", wholeNumberedDateDifference);
}
Output
bday is in less than a week 2
Demo

Related

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>

Return string: 'Days' or 'Months' or 'Years' based on N of days

I am using moment.js on vue 3.
I have a function that returns the difference of two dates. This is working but it is related to my question. The function is written like this:
methods: {
getDateDifference (date) {
var date = moment(date, 'YYYY MM DD')
var today = moment()
return today.diff(date, 'days')
}
}
Now, I can successfully get the difference of the date passed on to the function from the date today. The problem is I want to check if the days returned is greater than a week but less than a month, so that I can convert it to weeks rather than displaying 28 days etc.. If also possible I want to include if months should be used instead of weeks when the days returned is greater than 28 days and render it also by years if the days returned is greater than or equal to a year
In short, instead of displaying
26 days
I need it to check if it can be converted into weeks so I can render it as:
3 weeks
Or if I can render it in months or year.
moment can deal this through the duration api:
Get the miliseconds:
methods: {
getDateDifference (date) {
var date = moment(date, 'YYYY MM DD')
var today = moment()
return today.diff(date)
}
}
Then use:
/* don't know the object holding methods */
let shortest = moment.duration(obj.methods.getDateDifference(date)).humanize();
Will get you months instead of days if it exceeds a month's time, years instead of months, etc.
Moment.js duration.humanize
You can use an array that associates thresholds in days with the granularity of your result.
const periods = [
{threshold: 366, period: "years"},
{threshold: 31, period: "months"},
{threshold: 7, period: "weeks"},
{threshold: 0, period: "days"}
];
days = today.diff(date, "days");
for (let i = 0; i < periods.length; i++) {
if (days >= periods[i].threshold) {
return today.diff(date, periods[i].period);
}
}

Age verification based on birthdate and current date

So, I need to calculate age by subtracting "todays" date from the converted date of an input field, entered by the user. Although it needs cleaned up, the below code works, I had to get creative as RN uses a different JS execution environment... see here.
My question, without adding the "+1" to this snippet "b.getMonth() + 1", the math on the date subtraction comes back 1 month off every time. When I add the "+1" it works like a charm, why? If it's a logical fix, I don't mind keeping the "+1," but I would surely like to know why the "+1" is necessary.
Also, totally open to improved solutions to this problem, keep in mind I had a much simpler function that worked great while debugger was open, once closed, it did not work, see the link above.
getVerifyBirthday(birthday) {
const b = new Date();
var verify = birthday.length;
const utc2 = Date.UTC(b.getFullYear(), b.getMonth() + 1, b.getDate());
if (verify === 10) {
const splitBirth = birthday.split('-');
var mydate = new Date(splitBirth[2], splitBirth[0], splitBirth[1]);
const a = mydate;
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const diffTime = Math.abs(utc2 - utc1);
const diffYears = (diffTime / (3.15576e+10));
this.setState({ diffYears: diffYears});
return diffYears >= 13;
} else {}
}
Update
I ended up refactoring my original function, leaving the (+1) on months due to months starting at 0, as mentioned below. I still had to split both dates, when I didn't split both, my age came back NaN/Undefined; not sure if this goes back to the RN execution environment vs browser, but I digress.
I'd make a few of my own suggestions over here:
there's no need to hussle with UTC dates (to make sure whether the user in his timezone has already reached the age of 13, assuming along the way that he or she was born in that same timezone ;)
there's no need to split mm-dd-yyyy date string to convert into Date, it may be parsed by new Date()
counting years as 365.25 days has certain error margin depending on the exact leap years quantity that passed since the user's birth year, instead whole years may be compared together with dates
To me, it makes more sense to decompose date strings into days, months and years and make decision based on full years difference minus 1 year (if the person didn't yet celebrate his/her birthday this year):
const today = new Date().toISOString().slice(0,10), // yyyy-mm-dd
birthday = '1982-06-21',
[bYear, bMonth, bDay] = birthday.split('-'),
[tYear, tMonth, tDay] = today.split('-'),
diffYears = tYear - bYear - (bMonth > tMonth || bDay > tDay ? 1 : 0)
console.log(diffYears)
.as-console-wrapper{min-height:100%;}
Months are zero-based in JavaScript Date objects. However, if you get a formatted string, they start from 1:
const date = new Date(2020, 1, 17); // 17th of February 2020
console.log("getMonth:", date.getMonth()); //month is 1
console.log("formatted:", date.toISOString()); //month is 2
So, actually what happens is that you're shifting both dates a month forward. This sort of works:
const originDate = new Date(2020, 1, 17); // 17th of February 2020
const originString = "2020-02-17".split("-");
const dateFromDate = new Date(originDate.getFullYear(), originDate.getMonth() + 1, originDate.getDate())
const dateFromString = new Date(originString[0], originString[1], originString[2])
console.log("dateFromDate:", dateFromDate); //month is 3
console.log("dateFromString:", dateFromString); //month is 3
When you do the subtraction it evens out but you can still run into an overflow of the date for months with different number of days:
const originDate = new Date(2020, 0, 31); // 31st of January 2020
const dateFromDate = new Date(originDate.getFullYear(), originDate.getMonth() + 1, originDate.getDate())
console.log("dateFromDate:", dateFromDate); // 1st of March 2020
This still works logically for most cases, however you are bound to run into a problem at some point if you shift months forward. So, instead you should be doing the opposite and subtracting 1 when converting a 1-based number into a Date object:
const originString = "2020-02-17".split("-");
const dateFromString = new Date(originString[0], originString[1] - 1, originString[2])
console.log("dateFromString:", dateFromString); //month is 2

moment - Issue with fromNow

Hey I'm trying to calculate user age using Moment.
function getAge(birthDate) {
const start = moment(birthDate, "YYYY-MM-DD");
const end = moment();
return start.to(end, true);
}
Before the month of may, the result is 1 year over:
getAge("1989-01-01"); // 31 years
getAge("1989-05-01"); // 30 years
example: https://codesandbox.io/s/nostalgic-tesla-bzb3d
the behavior is the same using fromNow instead of to
do you have any idea on how to solve this?
The result of .to() is a relative time string and it internally rounds the values in either direction (so 29.5 years would become 30 years). It is meant for display purposes like showing "posted 3 minutes ago" in forum or blog posts.
You are looking for the .diff() method that calculates the difference, but will only account for full units of measurement provided by the second argument, i.e. truncating the actual number.
const start = moment(birthDate, "YYYY-MM-DD");
age = moment().diff(start, "years");
https://codesandbox.io/s/naughty-lewin-e6vrt
You may use diff function like:
function getAge(birthDate) {
const start = moment(birthDate, "YYYY-MM-DD");
const end = moment();
return end.diff(start, 'years');
}
console.log(getAge("1989-01-01"));
console.log(getAge("1989-05-01"));

How to return the users age from their year of birth

These are the criteria for the question;
return the user's age as a number.
you can assume that the passed user will always have a 'yearOfBirth' property.
my answer was;
function getUserAge (user) {
const year = new Date().getFullYear()
return year - user.yearOfBirth
}
I am unsure to why this does not return the users age. Does anyone have any advice?
my answer is tested against this
describe("getUserAge", () => {
it("returns the age of the passed user based on its yearOfBirth property", () => {
const currentYear = new Date().getFullYear();
const user = {
yearOfBirth: 1989
};
expect(getUserAge(user)).to.equal(currentYear - 1989);
});
});
You need to allow for whether the user has had their birthday yet this year. Your current implementation returns the same value in January and December for someone born in June, but of course, the December value should be one higher than the January value.
If all you have to work from is yearOfBirth, you cannot accurately return their age. You need the month and day as well. (Depending on how pedantic you want to be, also the hour, minute, and second; but most people aren't that pedantic.)
You can use the difference between timestamp of the date birth and now.
var date_birth= new Date("2010/11/3");
var timestamp_birth= date_birth.getTime();
var now= Date.now();
var diffTimestamp= now - timestamp_birth;
// ms to secend / second to minute / minute to hour / hour to days of year
console.log( diffTimestamp/1000/60/60/24/365 );
*as you can see, knowing only the year is not enough

Categories

Resources