Round a timestamp to the nearest date
Asked Answered
C

9

23

I need to group a bunch of items in my web app by date created.

Each item has an exact timestamp, e.g. 1417628530199. I'm using Moment.js and its "time from now" feature to convert these raw timestamps into nice readable dates, e.g. 2 Days Ago. I then want to use the readable date as a header for a group of items created on the same date.

The problem is that the raw timestamps are too specific - two items that are created on the same date but a minute apart will each have a unique timestamp. So I get a header for 2 Days Ago with the first item underneath, then another header for 2 Days Ago with the second item underneath, etc.

What's the best way to round the raw timestamps to the nearest date, so that any items created on the same date will have the exact same timestamp and thus can be grouped together?

Cotenant answered 5/12, 2014 at 20:2 Comment(1)
See the much more recent Javascript round date to nearest full date.Maness
L
2

Try this:

Date.prototype.formatDate = function() {
   var yyyy = this.getFullYear().toString();
   var mm = (this.getMonth()+1).toString();
   var dd  = this.getDate().toString();
   return yyyy + (mm[1]?mm:"0"+mm[0]) + (dd[1]?dd:"0"+dd[0]);
  };

var utcSeconds = 1417903843000,
    d = new Date(0);

d.setUTCSeconds(Math.round( utcSeconds / 1000.0));

var myTime = (function(){
        var theTime = moment(d.formatDate(), 'YYYYMMDD').startOf('day').fromNow();
        if(theTime.match('hours ago')){
            return 'Today';
        }
        return theTime;
    })();

alert( myTime );

http://jsfiddle.net/cdn5rvck/4/

Legaspi answered 5/12, 2014 at 20:46 Comment(2)
This is very close to what I'm looking for...any way to get fromNow() to only use days as units? For example, if it's 3:00 PM currently and I create an item, Moment will display the date as 15 hours ago even though I just created it (because it's basing that off of the distance from 12:00 AM). I'd like it to just say Today.Cotenant
@daGuy I have updated the code. Does that answer your question?Legaspi
L
39

Well, using js you can do:

var d = new Date(1417628530199);
d.setHours(0);
d.setMinutes(0);
d.setSeconds(0);
d.setMilliseconds(0);

A shorter way of writing it is d.setHours(0, 0, 0, 0);

Levite answered 5/12, 2014 at 20:7 Comment(12)
I think your faster method is incorrect because you use the timezone offset for the current moment in time, as opposed to the timezone offset that applies to the given timestamp. You should change the second adjustment to timestamp.getTimezoneOffset().Haig
@MichaelPetito timestamp is an integer so guess you meant new Date(timestamp).getTimezoneOffset() ? Still, timezoneOffset will be the same as new Date()Levite
The timezone offset can vary, and getTimezoneOffset gets the offset for the date instance. For example, I am in US Eastern Timezone, and new Date().getTimezoneOffset() is 300 for today's date, but new Date("6/1/2016") is 240 because of change of daylight savings. Your code may give unexpected results if the date represented by the timestamp has a different timezone offset from the current date. E.g. roundDate(new Date("6/1/2016").getTime()) results in the date Wed Jun 01 2016 01:00:00 GMT-0400 (Eastern Daylight Time) - not midnight - for me today.Haig
@MichaelPetito I see, hadn´t considered that. Feel free to edit answerLevite
Thanks for both methods, the second may have better performance, but the first seems to be more readable and understandable.Cavorilievo
@Cavorilievo yeah it´s the one I usually use :)Levite
This doesn't round to the nearest date, it floors to the current date and is equivalent to just setting the hours, minutes, etc. to 0. Messing around with the timezone offset is inefficient and only necessary because the algorithm effectively zeros the UTC date, so the offset has to be add to zero the local date.Maness
@Maness yes it doesn't round to nearest date because if you check in the end of OP question, he states " so that any items created on the same date will have the exact same timestamp and thus can be grouped together". If I had rounded, timestamps from same date would not have the same timestamp. It is indeed equivalent to setting hour/second/minute etc to 0, and that method is also in my answer but at the time I answered, timezone way was faster.Levite
For me new Date(2021, 0, 1, 0, 30, 0, 0) is "rounded" to 31 December, not 1 Jan. The function is called roundDate but it doesn't. When setting the time, setHours(0,0,0,0) is considerably more efficient than setting each part separately. All methods are about the same speed and at about 5,000 iterations per ms irrelevant. setHours is much more semantic and easier on maintainers than %ing a time value and messing with offsets (which is only required because of the remainder operation). Likely it fails on days where a DST changeover occurs.Maness
@Maness I'm not sure why are you arguing about this 7y old answer. I provided both methods, if you read the comments I mention I do use the setHours option because its more clear, the name of the function should be floorDate maybe but it does what the OP wanted it to do, which wasn't actually to round to nearest date. setHours(0,0,0,0) is even cleaner and already an alternative answer, I don't think changing my answer now to copy that is fairLevite
Because the timezone shifting answer demonstrably produces incorrect results and is recommended as faster. This post has many upvotes so clearly others think it's OK and don't realise the issues. Hopefully if you don't fix it the comments will be sufficient.Maness
@Maness You are right, math way can have wrong results. Removed it from answerLevite
S
14

Using Moment.js, you can use the following code to round everything to the beginning of the day:

moment().startOf('day').toString();
// -> Prints out "Fri Dec 05 2014 00:00:00 GMT-0800"

You can read more about startOf() in the docs.

Surrogate answered 5/12, 2014 at 20:10 Comment(3)
@Ken Fyrstenberg - Thanks for the edits! I was thinking in Ruby mode for some reason. I wish I could upvote edits to give you some rep :-)Surrogate
No worries! +1 for nice solution :)Roberts
This doesn't answer the question of "round to the nearest date"Casa
H
12

Here is a clean way to get just the date in one line with no dependencies:

let d = new Date().setHours(0, 0, 0, 0);
Hydroxyl answered 5/11, 2019 at 5:55 Comment(2)
Except that d will be a time value (ms from ECMAScript epoch), not a Date object. To get a Date you need new Date(new Date().setHours(0, 0, 0, 0)). ;-)Maness
This ignores the whole point of the question. It doesn't "round to the nearest date" ... in fact you could be up to 23 hours, 59 minutes, and 59 seconds wrong!Casa
H
5

Just construct a new Date from the existing one using only the year, month, and date. Add half a day to ensure that it is the closest date.

var offset = new Date(Date.now() +43200000);
var rounded = new Date(offset .getFullYear(),offset .getMonth(),offset .getDate());
console.log(new Date());
console.log(rounded);

Since this seems to have a small footprint, it can also be useful to extend the prototype to include it in the Date "class".

Date.prototype.round = function(){
    var dateObj = new Date(+this+43200000);
    return new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate());
};
console.log(new Date().round());

Minimized:

Date.prototype.round = function(){var d = new Date(+this+43200000);return new Date(d.getFullYear(), d.getMonth(), d.getDate());};
Havener answered 29/6, 2018 at 22:57 Comment(4)
That just trims date, not rounds it.Unreality
@Unreality - Thanks for reminding me of this. It was missing +.5 to the date for rounding, but I never updated it here. Cheers!Havener
How does adding half a day help you round the date?Casa
@Casa - If you were to construct a years worth of dates like this, without using a half day offset, you would get thrown off by daylight savings adjusting the time plus or minus one hour. That effect causes unforeseen side effects such as being the wrong day, or being not quite at 0:00, which can create misses in searches or data anomalies during analysis.Havener
L
2

Try this:

Date.prototype.formatDate = function() {
   var yyyy = this.getFullYear().toString();
   var mm = (this.getMonth()+1).toString();
   var dd  = this.getDate().toString();
   return yyyy + (mm[1]?mm:"0"+mm[0]) + (dd[1]?dd:"0"+dd[0]);
  };

var utcSeconds = 1417903843000,
    d = new Date(0);

d.setUTCSeconds(Math.round( utcSeconds / 1000.0));

var myTime = (function(){
        var theTime = moment(d.formatDate(), 'YYYYMMDD').startOf('day').fromNow();
        if(theTime.match('hours ago')){
            return 'Today';
        }
        return theTime;
    })();

alert( myTime );

http://jsfiddle.net/cdn5rvck/4/

Legaspi answered 5/12, 2014 at 20:46 Comment(2)
This is very close to what I'm looking for...any way to get fromNow() to only use days as units? For example, if it's 3:00 PM currently and I create an item, Moment will display the date as 15 hours ago even though I just created it (because it's basing that off of the distance from 12:00 AM). I'd like it to just say Today.Cotenant
@daGuy I have updated the code. Does that answer your question?Legaspi
X
1
function roundDownDate(date) {
  if (typeof date !== "object" || !date.getUTCMilliseconds) {
      throw Error("Arg must be a Date object.");
  }
  var offsetMs = date.getTimezoneOffset() * 60 * 1000,
      oneDayMs = 24 * 60 * 60 * 1000;
  return new Date(Math.floor((date.getTime() - offsetMs) / oneDayMs) * oneDayMs + offsetMs);
};

This should work and is pretty fast.

Xanthine answered 27/9, 2016 at 2:52 Comment(0)
I
0
function getDateOfTimeStamp(time) {
  var originTime = 0;
  var offsetOriginTime = originTime + new Date().getTimezoneOffset() * 60 * 1000;
  var timeSinceOrigin = time - offsetOriginTime;
  var timeModulo = timeSinceOrigin % (24 * 60 * 60 * 1000);
  var normalizedTime = time - timeModulo;

  console.log(new Date(normalizedTime) ,new Date(time));
  return normalizedTime;
}

This worked for my project. Pure math, no string manipulation needed, no external lib needed, so it's super fast.

You can try by copying the above function to javascript console and then do normalizeTimeToDate(Date.now())

Idalia answered 26/2, 2017 at 7:25 Comment(0)
C
0

I find very simple and intuitive solution to be:

const d = new Date(Math.floor(Date.now() / 86400000) * 86400000);
// 86400000 = 24 * 60 * 60 * 1000

Also if you wish to round to other values, for example to whole hours, you can easily expand the mentioned solution to:

const d = new Date(Math.floor(Date.now() / 3600000) * 3600000);
// 3600000 = 60 * 60 * 1000
Chancemedley answered 9/2, 2022 at 10:12 Comment(2)
Math.floor always rounds down, that's the point of the function.Casa
Although this is slightly more performant than .setHours(0, 0, 0, 0); sadly it calculates in UTC+0, if you're in another timezone the resulting timestamp won't be a midnight.Pavonine
M
0

Here is a way to get the nearest date for any time:

const time = new Date();
const date = new Date(new Date(time.setHours(time.getHours() + 12)).setHours(0, 0, 0, 0));

The idea is as follows:

  • Add 12 hours to the time
  • Round down to nearest date by removing the time component. By adding the 12 hours before we round down, we ensure that we effectively round to the nearest date.

Examples:

const time = new Date(2023, 10, 07, 11, 00, 00) // => date = new Date(2023, 10, 07)
const time = new Date(2023, 10, 07, 13, 00, 00) // => date = new Date(2023, 10, 08)
Melli answered 7/10, 2023 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.