0

I was trying to do a basic date calculation as seen below. I want to find the remaining days in the month given the arguments below.

I thought I determined a solution, but there appears to be an extra 1/4 in the answer.

// returns last day of month
function lastDayOfMonth(date) {
  return new Date(date.getFullYear(), date.getMonth() + 1, 0)
}

let day_start = new Date('2018-11-04');
let day_end = new Date('2019-01-10');
let day_last = lastDayOfMonth(day_start);


let total = 0;
if (day_end <= day_last) {
  total = day_end - day_start;
} else {
  total = day_last - day_start;
}
console.log(total / (1000 * 60 * 60 * 24)); // returns 26.25
  • Your date range includes the day that daylight saving time changes, so there's an extra hour. – Barmar Mar 28 '22 at 21:54
  • I get `26.208333333333332` (I'm in Eastern time in the US). – Heretic Monkey Mar 28 '22 at 21:57
  • calculations appear to be using unix epoch time, that is msec since 1970 and not be consistent ... according to @heretic –  Mar 28 '22 at 21:58
  • Ah javascript dates. When I heard quarter of a day, I immediately thought of leap year. Each calendar year is actually a quarter day short of an orbital year, so leap year adds a day to make up for it. – Jason Williams Mar 28 '22 at 22:01
  • `Date` objects are stored as exactly that: milliseconds since the epoch in UTC (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) they are stored that way consistently though. – Heretic Monkey Mar 28 '22 at 22:01
  • Try logging `day_start`, `day_end`, and `day_last` and I think you'll see the problem. `day_last` is the beginning of the day UTC, the others are in the local timezone. – Barmar Mar 28 '22 at 22:01
  • @JasonWilliams Except it's not a quarter of a day for people in the Eastern timezone. For me it's `.2083333` – Barmar Mar 28 '22 at 22:02
  • For me it's exactly 26. But I'm in GMT+0 – phuzi Mar 28 '22 at 22:03
  • related ... syntax ... https://stackoverflow.com/questions/41948/how-do-i-get-the-difference-between-two-dates-in-javascript –  Mar 28 '22 at 22:03
  • 1
    So the problem is that you're adding in your timezone offset. The extra .25 is because you're 6 hours behind UTC. – Barmar Mar 28 '22 at 22:03
  • @phuzi - that is what I said. 6 hours, the UTC offset of my location. –  Mar 28 '22 at 22:08
  • @Barmar - throwing darts works some times! –  Mar 29 '22 at 01:02

2 Answers2

0

The difference comes from the UTC offset, since as you have it written, day_start and day_end are midnight UTC, but day_last is midnight in your local timezone.

To fix, just do all calculations in UTC. Since day_last is the only one that is local, this is the only one you need to adjust. In order to produce this in UTC, you could use the setUTCFullYear and related methods (sadly I don't believe there is a way to do it with the Date constructor):

// returns last day of month
function lastDayOfMonth(date) {
  // Set correct date in UTC:
  let last_date = new Date();
  last_date.setUTCFullYear(date.getFullYear());
  last_date.setUTCMonth(date.getMonth() + 1);
  last_date.setUTCDate(0);

  // Zero out the time so that this will end up as midnight:
  last_date.setUTCHours(0);
  last_date.setUTCMinutes(0);
  last_date.setUTCSeconds(0);
  last_date.setUTCMilliseconds(0);
  return last_date;
}

let day_start = new Date('2018-11-04');
let day_end = new Date('2019-01-10');
let day_last = lastDayOfMonth(day_start);


let total = 0;
if (day_end <= day_last) {
  total = day_end - day_start;
} else {
  total = day_last - day_start;
}
console.log(total / (1000 * 60 * 60 * 24)); // returns 26 flat
CRice
  • 29,968
  • 4
  • 57
  • 70
  • maybe simpler to just put the offset in for the 3rd function. Is there a method to retrieve UTC offset? –  Mar 28 '22 at 22:25
  • `new Date().getTimezoneOffset()` will return the offset in _minutes_, however keep in mind that the offset changes depending on which `date` is used, because of DST. – CRice Mar 28 '22 at 22:25
  • let offset = ( date.getTimezoneOffset() / 60 ); should be more concise. –  Mar 28 '22 at 22:38
  • DST only matters if you are not careful. If you want to add the offset inside `lastDayOfMonth`, you must ensure you use the offset of the constructed date, _not_ the offset of the `date` argument, as it could be one hour off due to DST change between the two times. Personally I don't like reasoning about DST as it often get it wrong. Hence my instinct is always to do everything in UTC, but YMMV. – CRice Mar 28 '22 at 22:39
  • DST should not effect UTC in any way according to ... https://stackoverflow.com/questions/5495803/does-utc-observe-daylight-saving-time –  Mar 28 '22 at 22:39
  • Yes, if you do everything in UTC, then DST does not matter. However using the offset to convert from local time to UTC may involve DST error when done naively, as I mentioned above. – CRice Mar 28 '22 at 22:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/243397/discussion-between-crice-and-jon-jon-bon-bon). – CRice Mar 28 '22 at 22:44
0

The Date constructor using the format, new Date(year, month, day) is evaluated using your local time zone. The Date constructor for the ISO 8601 string, YYYY-MM-DD is evaluated in UTC.

You can resolve this by printing your local date in ISO 8601 format and passing that into a newly constructed Date object.

function lastDayOfMonth(date) {
  let localDate=new Date(date.getFullYear(), date.getMonth()+1, 0);
  let m=localDate.getMonth();
  let d=localDate.getDate();
  return new Date(localDate.getFullYear()+'-'+(m<10?'0':'')+m+'-'+ (d<10?'0':'')+d);
}
phatfingers
  • 9,770
  • 3
  • 30
  • 44