1

I'm trying to get the millisecond value of the nearest absolute year in JavaScript, replying only on the valueOf() method of the JavaScript Date object.

For example: today is Monday April 4th 2016 at 12:50am. So I am looking for the nearest absolute year (in the past). January 1st, 2016, 00:00am.

Here is the code I have:

var ms_per_year = 31536000000;
var now = new Date().valueOf();
var mod_year = now % ms_per_year;
var nearest_absolute_year = now - mod_year;

console.log(new Date(nearest_absolute_year));
// Sun Dec 20 2015 19:00:00 GMT-0500 (EST)
console.log(new Date(Date.parse('2016 1 1 00:00:00')));
// Fri Jan 01 2016 00:00:00 GMT-0500 (EST)

I would expect the two printed dates to be the same, as they are with minutes:

var ms_per_minute = 60 * 1000;
var now = new Date().valueOf();
var mod_minute = now % ms_per_minute;
var nearest_absolute_minute = now - mod_minute;

console.log(new Date(nearest_absolute_minute));
// Mon Apr 04 2016 00:57:00 GMT-0400 (EDT)
console.log(new Date(Date.parse('2016 4 4 00:57:00')));
// Mon Apr 04 2016 00:57:00 GMT-0400 (EDT)

How can I calculate the milliseconds passed since 1970 and the beginning of the current year with without using Date.parse(), relying solely on math?

3
  • You wouldn't use Date.parse anyway. If you have a time value you can pass that directly to the Date constructor. But you don't want to do that… The algorithm is to work out the number of days since 1970-01-01T00:00:00Z to whenever and multiply by 8.64e7. Commented Apr 4, 2016 at 5:32
  • I would like to know why you stick the way to get what you want. Commented Apr 4, 2016 at 5:36
  • 31536000000 is equivalent to 365 days, you are forgetting leap years, of which there have been 11 since 1970 (not including 2016) so your date is 11 days out. getMinutes just gets the minute component of the time, not minutes since epoch, so as long as you subtract whole days (or whole hours really) it will not change. Commented Apr 4, 2016 at 5:46

3 Answers 3

1

You need to deal with leap years, Himanshu is on the track for an elegant solution, a simple loop will do the trick but is not so efficient:

/* @returns {number} time value for start of current year
** Don't use Date methods
** Assumes current time is after epoch (1970-01-01T00:00:00Z)
*/
function getStartOfYear(timeValue) {
  var timeValue = timeValue || Date.now();
  var accumulatedTime = 0;
  var day = 8.64e7;
  var year = 1970;
  var msForYear = 365*day; // ms for 1970
  function isLeap(n) {
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
  }
  
  // Continue while adding ms for current year won't go past limit
  while ((accumulatedTime + msForYear) < timeValue) {
  
    // Add time for current year
    accumulatedTime += msForYear;
    
    // Calculate time for next year
    msForYear = (isLeap(++year)? 366:365) * day;
  }
  
  // Return accumulated time
  return accumulatedTime;
}

// Tests
['2010','2011','2012','2013','2014','2015','2016','2017'].forEach(function(y){
  //Generate time value away from start of year
  var startOfYear = new Date(getStartOfYear(new Date(y,3).getTime() + 23000));
  document.write('<br>UTC start of year: ' + startOfYear.toISOString());
  startOfYear.setMinutes(startOfYear.getMinutes() + startOfYear.getTimezoneOffset());
  document.write('<br>Local start of year: ' + startOfYear);
});
body {
  font-family: courier, mono-space;
  font-size: 90%
}

Edit

Here is a non–looping solution. It works in UTC as above, but can be adjusted to local as suggested there too.

function getStartOfYear(timeValue) {
  timeValue = +timeValue || Date.now();
  // ms for one day
  var day = 8.64e7;
  // ms for standard year
  var year = 365 * day
  // ms for leap block
  var leapBlock = year * 4 + day;
  // Use 1969-01-01T00:00:00Z as epoch
  timeValue += year;
  // Accumulate time
  var accumulatedTime = 0;
  accumulatedTime += Math.floor(timeValue / leapBlock) * leapBlock;
  accumulatedTime += Math.floor((timeValue % leapBlock) / year) * year;
  // Reset epoch to 1970-01-01T00:00:00Z and return time value
  return accumulatedTime - year;
}

And if you want obfuscated, but concise, code, try:

function getStartOfYear(timeValue) {
  timeValue = +timeValue || Date.now();
  var day = 8.64e7, year = 365 * day, leapBlock = year * 4 + day, accumulatedTime = 0;
  return ((timeValue + year) / leapBlock | 0) * leapBlock + ((timeValue + year) % leapBlock / year | 0) * year - year;
}
Sign up to request clarification or add additional context in comments.

4 Comments

this is good but the question requires a solution purely based on maths
I said it because it contains loops, conditional operations etc. and not pure mathematics.
This is very interesting @RobG, I will dig into this tonight.
@RobG, smashing answer! Testing with: console.log(new Date(getStartOfYear(Date.now()))); and get Thu Dec 31 2015 19:00:00 GMT-0500 (EST), which is what I expect. What definitely I did not expect was such an elegant solution to show up so soon.
1

This should do the trick:

var d = new Date(new Date().getFullYear(), 0);
d.valueOf(); // > 1451634391371 (ms)

3 Comments

This looks like the most readable and least error prone solution. You may need to set hours, minutes, seconds and milliseconds too, but that's still quite trivial.
The OP wishes to avoid Date methods (why is a mystery). Otherwise, getting the start of the year for any time value is as simple as new Date(new Date(value).getFullYear(), 0).
@Kutyel, .getFullYear() is cheating :) - @RobG, I want to normalize what's going on under the hood of the JS Date object, without using the JS Date object to do it, why? Honestly: because it's an interesting problem to me.
0

You're logic is not working due to the following assumption

var ms_per_year = 31536000000

For a year with 365 days this is the correct value of milliseconds, but you forgot to take leaps years into account.

The following code will help

var ms_per_year = 31536000000;
var ms_per_day = 86400000;
var now = new Date().valueOf();
var mod_year = now % ms_per_year;
var year = Math.floor(now/ms_per_year);
var leap_years = Math.floor(year/4);
var nearest_absolute_year = now - mod_year;
var actual_value = nearest_absolute_year + (leap_years*ms_per_day);
console.log(new Date(actual_value)); 

correct me if you find any mistakes.

4 Comments

This returns me Fri Jan 01 2016 01:00:00 GMT+0100 (CET), which is still not the correct time. It would be correct though if the requested timestamp should be UTC.
This will fail for dates like like 2013-04-01 as it doesn't accurately count the number of leap years. Also, new Date().valueOf() would be better as Date.now.
@Arjan—you can adjust the minutes for the host time zone offset, so actual_value.setMinutes(actual_value.getMinutes() + actualValue.getTimezoneOffset()). The sign is odd because ECMAScript timezone values are the opposite of ISO 8601, so -ve for east and +ve for west.
@RobG, why would Date.now() be better than Date.valeOf()?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.