How to prevent Rules scheduled within a month may have an offset that is off by 1 or 2 days? I have been using Rules to send out emails to users based on their user role. Based on the role, they should get emails at different times based on an event.

Whenever a rule is triggered in a month with more or less than 30 days however, the offset doesn't seem to work and my scheduled events are one day off. In the case of February (with 28 days), it's even two days off.

I'm assuming this is because after 30 days it calculates it as a month? I'm at a loss as to how to fix this.

These schedules are tied to certain events and should always be triggered a certain number of days after the event start date.
Understanding the issue

The explanation for what you're experiencing can be found in the description of the (open) issue about "Dates are poorly handled". Here is part of what it includes:

/**
 * Defines possible duration multiplier.
 */
function rules_ui_element_duration_multipliers() {
  return array(
    1 =&gt; t('seconds'),
    60 =&gt; t('minutes'),
    3600 =&gt; t('hours'),
    // Just use approximate numbers for days (might last 23h on DST change),
    // months and years.
    86400 =&gt; t('days'),
    86400 * 30 =&gt; t('months'),
    86400 * 30 * 12 =&gt; t('years'),
  );
} 


From the above code, it's obvious that there may be a discrepancy of some days if scheduling a rule X months from now (every month is assumed to have exactly 30 days, which is not the case of course). Similar problem when using years, and also a (minor) problem of 1 hour when using DST.

Making it work for MONTHLY executions

So far the bad news ... the good news is that there is a work around for this. If you use the Rules Once per Day module, you can take advantage of these additional facilities of this module (as mentioned on its project page):


  The module also provides a "Day of the week is a" Rules condition, to further restrict actions to being run on specific day(s) of the week, on working days only, or at weekends only.
  
  From version 7.x-1.1 there is now a "Day of the month" condition, too.


With that, you should be able to adapt any rule to perform its Rules Actions only once a week, or once a month.

Making it work for YEARLY executions

But, there is even more, for those cases where you want a rule to only execute once a year, you can use a Rule as explained in this issue and which looks like so:


Create a rule triggered by the "Once per day" event
Add a "Current day of the month" condition:


Comparison "equals"
Day of the month value "1"

Add a "Text comparison" condition:


Data selector "site:name" (dummy text, not used)
PHP evaluation "return date('m');" ( ! )
Matching text "^01$" (for January)
Comparison operation value "Regular expression"



The above logic will ensure that your Rules Action(s) are executed on exactly the 1st of January of every year (adapt the "1" and/or "January" to fit whatever other day of a year).

( ! ): I admit, this solution (only required for "YEARLY" though) requires the PHP filter to be enabled, which I try to avoid at any cost (I've not found an alternative for this yet ...).

PS: I'm assuming this question is about D7.