moment / luxon

⏱ A library for working with dates and times in JS
https://moment.github.io/luxon
MIT License
15.23k stars 730 forks source link

Add weekmonth property #1469

Open joezappie opened 1 year ago

joezappie commented 1 year ago

Is your feature request related to a problem? Please describe. I'd like to use luxon to generate common holidays for the year. Most holidays are easily created with the current DateTime units, but things like "First Monday of the month" don't seem possible.

Describe the solution you'd like Add a unit for weekmonth (or monthweek?). Possible options would be positive numbers to set from the start of the month, or negative numbers to set from the last of the month:

// Labor Day - First Monday in September
DateTime.now().set({ month: 9, weekday: 1, weekmonth: 1 })

// Memorial Day - Last Monday in May
DateTime.now().set({ month: 9, weekday: 1, weekmonth: -1 })

Add a getter as well:

const dt = new DateTime.now(); // 7/17/2023
dt.get('weekmonth'); // 4

Describe alternatives you've considered I can add some logic into my parser to pull out that property and generate it myself, but I think this is a generic enough feature to warrant it being included in luxon directly. There is also a luxon business days plugin which allows for defining holidays, but it requires replacing luxon everywhere in your code with their version. Not a huge fan of that.

Additional context

Another benefit of this being directly in luxon, instead of doing something such as DateTime.now().set({ month: 5 }).endOf('month').startOf('week').set({ weekday: 1}) is that it can be stored in a database such as mongo easily. Also that just feels over complicated.

I currently check if a particular day is a holiday by looping through some rules defined in my db:


const rules = [
    {
        "_id": "new-years",
        "name": "New Year's Day",
        "rules": {
          "month": 1,
          "day": 1
        }
      },
      {
        "_id": "memorial-day",
        "name": "Memorial Day",
        "rules": {
          "month": 5,
          "weekday": 1,
          "weekmonth": -1,
        }
      },
      {
        "_id": "independence-day",
        "name": "Memorial Day",
        "rules": {
          "month": 7,
          "day": 4,
        }
      },
      {
        "_id": "labor-day",
        "name": "Labor Day",
        "rules": {
          "month": 9,
          "weekday": 1,
          "weekmonth": 1,
        }
      }
];

const days = interval.splitBy({ days: 1 });
const holidays = [];

days.forEach((date) => {
    rules.forEach((holiday) => {
      for (let prop in holiday.rules) {
        if (date.s.get(prop) !== holiday.rules[prop]) {
          return;
        }
      }
      holidays.push(holiday);
    });
});