As a random example, Azure generally bills for resources on an hourly basis.
Suddenly, months, years, calendars, etc... cease to matter. Hours are hours. You can use UTC for everything, ignore time zones and daylight savings, and aggregate however you like, or how the customer likes.
Similarly, consumption-based plans are relatively straightforward. Simply multiply unit costs with the per-unit costs. You can even blend this into the hourly model above, where each hour has a "units consumed this hour" and a "unit price per hour". This way you can handle things like changes to the unit price fairly accurately.
With cut-overs between plans, the trick is to just bill for them as separate line items instead of one thing going through transformations. E.g.: 220 hours of Standard SKU and 500 hours of Premium SKU this month. It doesn't matter how many times the customer changed their mind this month that way.
A nice trick is that data like the above superficially sounds like it'll be huge in a database, but column-based compression works wonders on it. Any analytics database engine, columnar format, or reporting engine should reduce it by at least 30:1 or more. Also, you now have a ready-made reporting system that allows slicing and dicing! In some database engines like SQL Server, this is as simple as making the main billing table use a Clustered ColumnStore index. In more cloud-native land you could use Parquet files in S3 buckets, or whatever.
I'm not saying that complexity doesn't remain, it's just that complexity shouldn't be voluntary. Copy a working model that matches your industry, and then only the essential complexity will remain.
An example of a hard-to-solve essential complexity is currency conversions and taxation when dealing with international customers. That's an externally imposed set of problems that can't be hand-waved away.