Nightshift: why Friday's gig is on Saturday
Every generic calendar tool thinks in calendar days. The music industry thinks in nights. Here's why we built a Stagent feature called Nightshift.
A DJ plays a Friday night slot at a festival. Their set time is 02:00. Technically, that's Saturday. Culturally, contractually, and in every conversation anyone in the industry will ever have about that gig, it's Friday. The booking was for Friday, the promoter's lineup poster says Friday, the artist's schedule says Friday, and the invoice will say Friday. But the timestamp in the database says Saturday, and every generic calendar tool on the planet will put the gig on the wrong day.
This is a mismatch that's existed for as long as nightlife has existed. It's also the kind of problem that breaks every off-the-shelf scheduling product the first time you point it at a touring artist's calendar. We ran into it early at Stagent, the artist management and booking platform I've been building for the last few years, and we eventually shipped a feature for it called Nightshift. I want to walk through what it does, why it needs to exist, and why nobody who hasn't lived in this industry would ever think to build it.
The mental model mismatch
The phrase you'll hear everywhere in the music industry is 'Friday's gig'. Not 'the set on May 5' and not 'the 2 AM slot'. Friday's gig. Friday is a night, and nights extend into the early morning of the next calendar day, and everybody accepts this without thinking about it. Promoters talk this way. Agents talk this way. Artists talk this way. It's the shared mental model of an entire industry. I call it the human clock. The one that doesn't roll over at midnight.
The shared mental model of every database is different. UTC doesn't care about your nightlife. Carbon doesn't know that 3 AM is still Friday night in Berlin. A calendar widget grouping events by day will cheerfully put your 02:00 set on Saturday, and now your promoter's itinerary, your artist's travel schedule, your tax-relevant booking date, and your invoice line all disagree with each other by one day.
This is not a bug that a generic calendar can fix by adding a toggle. It's a fundamental mismatch between how the industry thinks and how computers sort time. The only place you can solve it is inside a piece of software that's opinionated about the industry it serves. That's the bet we made with Stagent, and that's where Nightshift came from.
The 7 AM rule
The core of Nightshift is one constant and one rule.
class Nightshift
{
public const NIGHTSHIFT_CUTOFF_HOUR = 7;
public static function isNightshiftHour($time): bool
{
return $time->hour >= 0 && $time->hour < self::NIGHTSHIFT_CUTOFF_HOUR;
}
}
Seven AM. Anything starting between 00:00 and 06:59 local time is associated with the previous day. That's the entire idea.
The cutoff isn't random. It's roughly when nightlife actually ends in most of the cities we serve. Clubs close, after-parties wind down, artists head to hotels. By 7 AM local, nobody is still calling last night 'tonight'. Seven is the inflection point where the cultural day rolls over. We tried a few other values when we first shipped this and 7 AM was the only one that didn't generate support tickets from both directions, which is the most boring and most accurate way to tune a constant.
Once the rule exists, the interesting work begins. Every query in Stagent that asks 'what's happening on Friday?' has to understand that 02:00 Saturday might be Friday's answer. That means the rule can't live in a view layer. It has to live in the database.
The SQL that every date query runs through
The load-bearing piece of Nightshift is a single SQL expression we call getEffectiveStartsAtSQL. Every query that needs to group bookings by day runs through it. Here's the shape of it, with the noise stripped out:
if(duration AND hour(local_starts_at) < 7 AND disable_nightshift != 1,
date_sub(local_starts_at, interval 1 day),
local_starts_at
)
Translated: if the booking has a duration (meaning it's confirmed, not TBC), starts before 7 AM in the local timezone, and doesn't have Nightshift explicitly disabled, then return yesterday's date. Otherwise return today's. The real version handles timezone conversion from UTC, TBC bookings that store the event day directly, and a few other edge cases, but the core is three clauses.
Every filter in the app runs through this expression. 'Show me Friday's bookings' becomes a SQL query that understands the cultural definition of Friday. The filter for 'this week' becomes a week that ends at 7 AM Monday, not midnight Sunday. Calendar aggregation, invoicing date ranges, artist availability lookups, analytics, all of it. If you get this one expression right, you inherit the correct mental model across the rest of the app without having to teach every feature what a night is.
The cases where it needs to be wrong
Nightshift can't be a global truth. There are cases where it's wrong and the user is right, and the feature has to make room for that.
The obvious one is the per-booking override. A 06:00 soundcheck on Saturday is not part of Friday night. Nobody calls it that. It's a Saturday soundcheck that happens early in the morning because the venue only had that slot. We added a disable_nightshift boolean on the booking, and if it's set, the effective date bypasses the shift and uses the real local date. The toggle is only available when the time actually falls into nightshift hours, because offering it at 14:00 makes no sense.
The less obvious one is travel items. Bookings are performances. An itinerary is made of more than performances. It has flights, hotels, transport, and appointments. Our first implementation applied Nightshift to everything in the itinerary, which meant a 05:30 flight on Saturday showed up on Friday's itinerary. That's wrong. An early flight is a real date, not a nightlife date. A hotel check-in at 02:00 is a Saturday check-in even if the artist came straight from the club. Travel items live in calendar time, not nightlife time.
So we split the behaviour. Bookings default to Nightshift. Travel items default to their actual calendar date. Then we added a user-level display mode called ItineraryDisplayMode with two options, EventNight and ActualTime, and a toggle in the planning filters called 'Show actual times' for the users who want to see the literal calendar. The default is EventNight because 95% of our users want that. The override exists because the other 5% need the truth.
That's the general pattern for an industry-specific feature. Pick the model that matches how most of your users think, and then leave a clean escape hatch for the ones who need the other model, ideally labeled in a way that makes the trade-off explicit.
The DST wrinkle we fixed last week
The edge case that found us most recently was daylight saving time. Specifically, last weekend.
On spring-forward night, which Europe just ran through in the early hours of Sunday 29 March, the clock jumps from 02:00 to 03:00. A DJ playing a 23:00 slot with a four-hour duration should end at 03:00, but naive arithmetic on a Carbon datetime would add four real hours and land at 04:00, because DST ate one of the hours. That's not the set ending an hour later. That's the displayed time being wrong, on the one night of the year where most of our users are actually checking.
The fix was to compute the end time 'naively'. Parse the start as a string. Add the duration in minutes to the string. Reparse the result in the event timezone. Never let the DST-aware datetime math near the duration arithmetic. It's a one-method change in the Booking model, and it's the kind of fix that only makes sense if you already have the Nightshift scaffolding to hold it.
$naive_end = Carbon::parse(
$this->starts_at->format('Y-m-d H:i:s')
)->addMinutes($this->duration);
Ugly, but right. Every other approach either stretched the set across DST or misreported the end time by an hour twice a year. The bug was specific, the fix was specific, and the only people who will ever notice it are the ones whose livelihood depends on Stagent's booking display matching the contract.
Why nobody else will build this
The reason I wanted to write about Nightshift is that it's the purest example I have of why industry-specific software exists. Nightshift is not a feature Google Calendar will ever add. It's not a feature Cal.com will add. It's not a feature a generic booking SaaS will add. Not because they couldn't. Because the mental model of nightlife is invisible to anyone who hasn't lived inside it. You don't know to build Nightshift unless you've spent a Friday night watching an artist play to a room at 2 AM and then heard everyone, including yourself, call it 'Friday's set' the next morning.
The whole value of a vertical product is that it encodes a thousand small conventions like this. Each one is a constant, a SQL expression, a toggle, and a word the industry already uses. On their own, they're nothing. Together, they're the reason users choose a product built for them over a horizontal tool with more features.
Nightshift looks modest from the outside. A constant. A SQL expression. A pair of enum values. A toggle. Look at the code in isolation and you'd be forgiven for thinking it's a nice-to-have. It isn't. Every calendar view in Stagent sits on top of it. Every filter, every itinerary, every invoicing date range, every artist availability query, every weekly planning screen. Rip Nightshift out and the entire product stops making sense to its users within a single click. The feature looks small. The weight it carries is the whole thing.
That's the part of building vertical software I care about most. You find the three or four ideas that make the rest of the product legible to the people who actually use it, and you encode them so precisely that nobody ever has to think about them again. Nightshift is one of those. Stagent has a dozen more like it, all of them invisible to anyone who hasn't spent a weekend with a lineup on a poster. Together they're the reason the product still exists, and the reason nobody running a booking operation is going to swap it for a tool built by people who don't know what Friday night means.
Hi, I'm Mischa. I've been Shipping products and building ventures for over a decade. First exit at 25, second at 30. Now Partner & CPO at Ryde Ventures, an AI venture studio in Amsterdam. Currently shipping Stagent and Onoma. Based in Hong Kong. I write about what I learn along the way.
Keep reading: Everyone's giving Cowork everything, and thanking it after.