f1lt3r
f1lt3r

Reputation: 2223

Find previous year in JavaScript using only Date.valueOf()

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?

Upvotes: 1

Views: 150

Answers (3)

Kutyel
Kutyel

Reputation: 9084

This should do the trick:

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

Upvotes: 1

RobG
RobG

Reputation: 147343

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;
}

Upvotes: 1

Himanshu Tanwar
Himanshu Tanwar

Reputation: 906

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.

Upvotes: 0

Related Questions