0

Context: An Event Planning/Management Software Tool

Goal: To clone an event from a year previous - and provide the user a head start on selecting the start & end dates of the event.

Assumption: This new event is likely a repeat/year-over-year event. So, an Oct 2018 event would be cloned for Oct 2019.

Example: Event being cloned had a start date of: Thu, Oct 20, 2016 and an end date of: Thu, Nov 3, 2016 (this includes setup and teardown dates for the event)

Expected Result:

  • Number of days in date range is maintained. (14 days)
  • Day of the Week is maintained. (Thursday for both)

The code should return a new start date of: Thu, Oct 17, 2019 and an end date: Thu, Oct 31, 2019

Optionally: Another acceptable date range might be: Thu, Oct 24, 2019 and an end date of Nov 7, 2019

Possible logic: I think the expected result could be achieved by grabbing the month, week of that month, and day of that week - and constructing the new event date off of that, for the new year.

Considerations: I want to use the Moment.js library if at all possible

Use Case / Actual Design

Expected Result / Examples

Current Code: This is what we currently have, but it is not returning the expected result. Seems to just be subtracting a day regardless of the year difference.

const today = moment();
const thisYear = today.format('YYYY');

const old_start = moment('2018-10-08');
const old_end = moment('2018-08-12');

// if in same year, use current dates
if (thisYear === old_start.format('YYYY')) {
  console.log({
    start: new_start.format('YYYY-MM-DD'),
    end: new_end.format('YYYY-MM-DD')
  })
} else {  
  console.log({
    start: new_start.year(thisYear)
      .isoWeek(old_start.isoWeek())
      .isoWeekday(old_start.isoWeekday())
      .format('YYYY-MM-DD'),
    end: new_end.year(thisYear)
      .isoWeek(old_end.isoWeek())
      .isoWeekday(old_end.isoWeekday())
      .format('YYYY-MM-DD')
  })  
}

The resulting, incorrect dates:

Would appreciate any help on what we're doing wrong here - and how we can fix it. Thanks!

  • You can do math directly on the numeric parts of a date. If you add 1 to the year, you get adjusted (as necessary) dates for the next year, and you can then check the day-of-week to move the range forward or backward as necessary for your purposes. – Pointy Jan 08 '19 at 22:16
  • I believe your approach is valid, but there is something amiss with your code. Where do `new_start` and `new_end` come from? – Sami Hult Jan 08 '19 at 22:18
  • @Pointy, got it. So you'd recommend just adding a year to both the start and end date - and then somehow adjusting it forward or backwards to the _closest_ (maybe) matching day of the week? – Steve Witmer Jan 08 '19 at 22:24
  • Well the day-of-week adjustment depends on the semantics of your own time ranges. For example, some conferences take place around a particular time of the same month every year, and generally with a consistent pattern like "Thursday and Friday". Exactly how you'd *automate* finding the dates in the following year depends on what you know about the time ranges. The `.getDay()` API gets you the day-of-week (0 to 6, 0 is Sunday), so you can use that to adjust the day-of-month with `.setDate()`. – Pointy Jan 08 '19 at 22:33
  • @Pointy, Well - I CAN know the # of days between each date. That shouldn't be a problem. So - if I'm loading in on the Monday before my event date, and finished loading out on the Tuesday following my event (8 days) - I want to maintain that duration & day of week structure for the following year. Which API would allow me to adjust both sides of the date range to the proper starting day of the week. (if the # of days stays fixed - the end day of the week should be fine). Hope i'm tracking with you... – Steve Witmer Jan 08 '19 at 22:44
  • Could probably use something like this to grab the duration - and then only care about calculating that new start date appropriately - and then just add on the duration in days to find the end date for the relevant year.| https://stackoverflow.com/questions/9129928/how-to-calculate-number-of-days-between-two-dates `var start = moment("2018-03-10", "YYYY-MM-DD"); var end = moment("2018-03-15", "YYYY-MM-DD"); //Difference in number of days moment.duration(start.diff(end)).asDays(); //Difference in number of weeks moment.duration(start.diff(end)).asWeeks();` Thoughts @Pointy? – Steve Witmer Jan 08 '19 at 22:46
  • OK you can add 1 to the year, and then see what day of week a date lands on by then calling `.getDay()`. So if the 2018 date returns 1 (Monday), and the 2019 date returns 2, you know that you want to get the day-of-month with `.getDate()`, subtract 1, and then pass that to `.setDate()`. Note that JavaScript (and Moment) will "fix" dates that roll back or forward into the previous or next month, so setting day-of-month to 0 means "last day of previous month" for example. – Pointy Jan 08 '19 at 22:48
  • Instead of comparing years as strings (which "works" but is ugly), use the *isSame* method, e.g. `thisYear.isSame(old_start, 'year')`. It would be good if moment implemented date parts as getters, `thisYear.year == old_start.year` is much more semantic. – RobG Jan 08 '19 at 23:17

1 Answers1

0

Here's a solution that takes a start date, end date, and number of years in the future and returns a future start and end date that

  1. Starts on the same day of the week as the original start date
  2. Starts within three days of the original start date
  3. Has the same number of days between the future start and end dates as were between the original start and end dates

The logical steps are:

  1. Approximate the future start date by adding the number of input years to the original start date
  2. Determine the difference between numerical DOW of the original start date and the approximate future date
  3. Convert the following table into a formula:

.

+----------------+-----------------------------------------------+
| DOW difference | Days to subtract from approximate future date |
+----------------+-----------------------------------------------+
|      -6        |        1                                      |
|      -5        |        2                                      |
|      -4        |        3                                      |
|      -3        |       -3                                      |
|      -2        |       -2                                      |
|      -1        |       -1                                      |
|       0        |        0                                      |
|       1        |        1                                      |
|       2        |        2                                      |
|       3        |        3                                      |
|       4        |       -3                                      |
|       5        |       -2                                      |
|       6        |       -1                                      |
+----------------+-----------------------------------------------+

which I've codified as:

Math.abs(dowDiff) < 4 ? dowDiff : dowDiff + 7 * -Math.sign(dowDiff, daysAdd)
  1. Subtract the calculated number of days from the approximate future date to determine the actual future date
  2. Determine the difference in days between the original start and end dates and add them to the calculated future start date to get the future end date
  3. Return the calculated future start and end dates

const getFutureDates = (start, end, years) => {
  const dStart = moment(start);
  const dStartFuture = dStart.clone().add(years || 1, 'years');
  const dowDiff = dStartFuture.day() - dStart.day();
  const daysSub = Math.abs(dowDiff) < 4 ? dowDiff : dowDiff + 7 * -Math.sign(dowDiff, daysAdd);
  dStartFuture.subtract(daysSub, 'days');
  const days = moment(end).diff(dStart, 'days');
  const dEndFuture = dStartFuture.clone().add(days, 'days');
  return {
    start: dStartFuture,
    end: dEndFuture
  };
}

const tests = [
  {start: '2011-08-10', end: '2011-08-20', years: 8},
  {start: '2017-08-09', end: '2017-08-19', years: 2},
  {start: '2014-08-06', end: '2014-08-16', years: 5},
];

tests.forEach(({start, end, years}) => {
  const format = (s, e) => `${moment(s).format('YYYY-MM-DD')} to ${moment(e).format('YYYY-MM-DD')}`;
  const {start: fStart, end: fEnd} = getFutureDates(start, end, years);
  console.log(`${format(start, end)} => ${format(fStart, fEnd)}`);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.23.0/moment.min.js"></script>
ic3b3rg
  • 14,629
  • 4
  • 30
  • 53