There is also an assumption that handling time can be a pain in the you-know-where, so it’s normal that most applications try to use manage time only if needed, and usually in a very simple manner. UNIX timestamps are the norm, and time formatting is mostly exclusive to presentational aspects.
Your application may never use anything more than start and ending dates, and may be checking the next month from a given moment. While that’s fine, in the age of subscriptions some projects may be forced to handle time in complex manners. For example, one app may be willing to handle billing internally rather than giving away payment fees to gateways like Stripe, Braintree and many others.
Luckily for us, PHP has two tools very useful for calculate time back and forth: intervals and periods.
What’s an interval?
In PHP, the
DateInterval object exists to describe the duration of time. For example, 6 days, or 45 minutes. There is no start date, no end date, just duration.
This is very useful when you need to describe the duration of time without taking into account when it should started or ended. If you need relative time for the duration, you can add or substract the
DateInterval to any
The neat thing about
DateInterval is that it can be very simple to represent as a string: Years, months, days, hours, minutes and seconds. Unfortunately, there is no millisecond precision support, so you will have to handle that separately if that’s your (rare) case.
DateInterval object can be shared using a tiny ISO 8601 string, so we can save the
DateInterval in the database. There is no need to serialize the object instance itself to a dozens of characters, which makes it less transportable across systems reading a single source of truth.
One problem of
DateInterval is that it cannot be created as negative (going back through time). You have to manually set the
invert property to
1. This seems more like a patch to avoid unwanted behavior from a stored negative interval.
In other words, you will need to substract it yourself, which is safer in my opinion. Otherwise, you will need to extract the minus character if it’s present and then invert it.
Now that we have a grasp of how
DateInterval can help us, let’s go into subscriptions. How we can bill an user periodically each 45 days? That’s where
DatePeriod can help.
What’s a DatePeriod?
As the name implies,
DatePeriod describes multiple moments (periods) across time from an starting point, a given number of times.
DatePeriod to work, you must issue at least 3 things: the starting date, the interval, and (preferably) the number of occurrences. You can exclude the starting date from the list of recurrences later.
You can also put an ending date instead of the number of recurrences, but in my opinion, you should avoid it doing so.
DatePeriod exposes some methods and properties, but the real useful tool are loops. Since the
DatePeriod implements the Traversable interface, you can loop into each recurrence using
foreach. No need to advance the needle for each period manually or making weird calculations.
DatePeriod is also based on ISO 8601. You can save it as small string in the database, which will make it interoperable too if you are dealing with other applications written in other languages.
With the above, we can safely store the billing periods of an user that subscribes to our app with only one simple standard string:
- We don’t need to create ten rows for each billing period,
- make complicated functions to know when is the next or previous recurrence, or,
- add or substract intervals.
With very simple code, we can just get the next billing recurrence.
The end is tricky
DatePeriod is VERY useful when dealing with periodic moments. You can even exclude the starting period using the
EXCLUDE_START_DATE constant from the list of recurrences. The ending, though, must be handled with care.
The first is problem is simple: a
DatePeriod must have an ending, being that a number of recurrences, or a plain date. In other words, there is no “infinite periods”, so you if you’re going to hit the roof, update the period itself or simplify your solution by using
The second is a little more difficult to understand. If you set an ending as a date, and that date is equal or lesser than the final recurrence, it won’t count as these are rounded down. To avoid that pitfall, always try to use a number of recurrences instead of an ending date, or add one second to the ending date so it’s greater.
If you’re looking to manipulate date, intervals and periods, let me recommend the Carbon library to easy your pain. It includes very useful tools and will make your code easier to understand than plumbing the vanilla time objects of PHP.