Opening Hours Schema Done Right for Holidays and Special Hours


Your cafe closes early for a holiday, opens late for an event, or shuts down for a week in January, and somewhere along the way Google started showing the wrong hours. Short answer: the schema property built for exactly this, OpeningHoursSpecification, can express both a normal week and a dated exception, but the fix only holds if the markup on your site and the hours on your Google Business Profile actually agree, because the profile is almost always what a customer sees first.
This post walks through the exact format for normal hours and exceptions, real test blocks you can adapt, how holidays and special hours work differently between schema markup and the Business Profile itself, and the common errors that cause a validator to flag a block as broken.
The Format
OpeningHoursSpecification, as schema.org documents it, expresses a set of opening hours using a handful of specific properties: dayOfWeek, which takes one or more day names, opens and closes, which take times, and for exceptions, validFrom and validThrough, which take dates marking the window that specific block applies to. Times are expressed in a 24 hour format, and day names follow schema.org's own defined values rather than a freeform string.
A normal week is expressed as one or more OpeningHoursSpecification blocks, each covering one or more days that share the same opening and closing time. A cafe open the same hours every day of the week needs only one block listing all seven days. A cafe with different hours on weekends needs at least two blocks, one for the weekday pattern and one for the weekend pattern.
The exception mechanism, validFrom and validThrough, is what separates a normal week block from a special hours block. A block without those two properties is read as the standing, ongoing schedule. A block with validFrom and validThrough set is read as applying only within that specific date window, which is exactly the tool needed to express a single holiday closure, an extended winter break, or a short term change without disturbing the standing schedule the rest of the year.
It helps to think of the standing schedule and any exception blocks as two separate, coexisting layers rather than one being an edit to the other. The standing weekly blocks describe the ongoing default. An exception block with a validFrom and validThrough window describes a temporary override that applies only during that specific stretch of dates. A system reading the page correctly checks the current date against any exception blocks first, and falls back to the standing schedule when no exception applies. Understanding that layered structure makes it much easier to write correct markup, since the goal is never to edit the standing schedule for a temporary change, only to add a separate, dated block alongside it.
Examples
A normal week block, open the same hours every day, lists all seven day names in dayOfWeek with a single opens and closes pair. This is the simplest case and the one most cafe sites already have in some form, even if imperfectly written.
A cafe that stays open past midnight needs its closes time expressed correctly as the following day's early hour, since a naive same day closes value earlier than the opens value would read as invalid or backward to a validator. Getting this specific case right matters more than it might seem, since a late closing cafe is exactly the kind of business most likely to have this error already sitting unnoticed in its markup.
A cafe closed entirely on a specific day of the week, a Monday closure being common, should simply omit that day from every dayOfWeek block rather than including a zero length or placeholder time for it. Leaving Monday out of every block entirely is the correct way to express closed on Monday, not including a Monday block with matching opens and closes times, which some site builders generate incorrectly by default.
A cafe with two separate service windows in the same day, a morning window and a separate afternoon or evening window with a gap between them, needs two distinct OpeningHoursSpecification blocks for that day rather than one block spanning the whole range. Testing a split shift block through a standard structured data validator confirms this pattern validates cleanly as two separate blocks sharing the same day name.
It is worth double checking every one of these examples on your own actual page rather than assuming a template or site builder generated the correct version automatically. Many site building platforms generate a reasonable default block for a simple, single window, every day the same hours cafe, but noticeably more of them produce incorrect or incomplete markup for the less common cases, the past midnight close, the closed day, and the split shift being the three most likely to come out wrong without a manual check.
Specials
A holiday exception uses validFrom and validThrough set to the same single date, or a narrow date range, alongside whatever opens and closes time applies for that specific day, or no opens and closes at all if the cafe is closed entirely on that date. This is the correct, documented way to express a one time deviation from the standing schedule without editing or removing the normal week blocks.
It is worth being precise about a rule with real, direct consequences: the hours shown on your Google Business Profile and the hours expressed in your site's schema markup need to match. Google Business Profile Help's own pages on editing business hours and adding special hours describe the profile as the place most customers will see hours information first, meaning a mismatch between the profile and the site's schema is not simply a technical inconsistency, it is the kind of gap that leads directly to a customer showing up at the wrong time.
The practical workflow that follows from this is straightforward: whenever a holiday or special hours change is made in one place, the profile or the site, it should be made in the other at the same sitting, not on separate schedules or by separate people who might forget. A holiday closure entered into the Business Profile a week before the holiday but never mirrored in the site's schema markup leaves the site quietly wrong for as long as nobody notices.
One detail worth flagging honestly: neither the schema.org documentation nor Google's Business Profile Help pages, as reviewed for this post, state a fixed rule for how a holiday that repeats every year should be handled long term, whether that means updating the validFrom and validThrough dates annually or leaving a static block in place. Absent a documented rule either way, the safer practice is treating a repeating holiday exception as something to revisit and update every year rather than assuming a set once, done forever block will keep working correctly across years.
This gap in the documentation is worth taking seriously rather than shrugging off. A holiday block written once, with a specific year baked into its validFrom and validThrough dates, will simply stop applying on its own once that year passes, since the dates are fixed rather than repeating. Nothing about the schema automatically rolls those dates forward for you. Building a yearly reminder to check and update every recurring holiday block is a small, cheap habit compared to the cost of a customer showing up on a holiday expecting hours that quietly reverted to the old, unmarked default because nobody updated the dates.
Errors
The most common error worth checking for first is a closes time that reads as earlier than the opens time without the late night exception handled correctly, which a validator will typically flag with a warning about an invalid or unusual time range. This is the single most frequent mistake among cafes with any evening or late hours, since it is easy to enter a closing time without accounting for whether it technically falls into the next calendar day.
A second common error is a missing closes property entirely, leaving an opens time with nothing to pair it with. A validator will generally flag this as an incomplete specification, since a business hours block needs both a start and an end to mean anything concrete.
A third error worth checking for is overlapping blocks, two separate OpeningHoursSpecification entries covering the same day with conflicting times, which typically happens when a site has been edited multiple times over the years without anyone removing the older, now outdated block. A validator may not always catch this cleanly as an error, since two technically valid blocks can still contradict each other in a way that only a careful human read catches, which is exactly why running the actual page through a validator after any hours change is worth the extra few minutes.
A fourth error is a forgotten expiry, a special hours block with a validFrom and validThrough set for a holiday that has long since passed, still sitting live in the markup as though it still applies. This does not typically throw a hard validator error, since the block is technically well formed, but it is worth checking for manually on a regular schedule, since a stale special hours block sitting past its intended window can quietly confuse a system reading the page long after the holiday it was meant to describe has passed.
A Routine to Keep Hours in Sync
Build a short, repeatable routine around any hours change, rather than treating each one as a one off task handled differently every time. Whenever a holiday, event, or seasonal closure is decided, update the Google Business Profile first, since that is what customers see soonest, then update the site's schema markup in the same sitting, then run the updated page through a structured data validator to confirm the block still validates cleanly.
After the holiday or special window has passed, check back and confirm any temporary block with validFrom and validThrough has expired from view, or, for a repeating annual holiday, has been updated to the new year's correct dates rather than left pointing at last year's window. This closing step is easy to forget precisely because it happens after the moment of urgency has passed, which is exactly why it belongs in a written routine rather than relying on memory.
None of this routine requires special tools beyond a structured data validator, which is free and simple to use for a quick check after any change. The discipline that matters is doing the update in both places, profile and site, in the same sitting, and circling back after the special window closes, rather than any particular piece of software.
If more than one person can make these updates, an owner, a manager, whoever handles the website, write the routine down somewhere everyone can see it, rather than assuming it lives correctly in each person's memory. A short, shared checklist, update the profile, update the schema, validate, set a reminder to check back after the holiday, removes the guesswork of who was supposed to handle which half of the update, which is exactly the kind of small gap that leads to a profile and a site quietly disagreeing with each other for weeks before anyone notices.
Related Reading
More from Hillcane on this topic, plus the pages on the site that sit next to the work.
Frequently Asked Questions
How do I mark up holiday hours in schema?
Use an OpeningHoursSpecification block with validFrom and validThrough set to the specific date or narrow date range the exception applies to, along with whatever opens and closes time applies, or no opens and closes at all if the cafe is closed that day. This expresses a one time deviation without disturbing the standing weekly schedule.
Does OpeningHoursSpecification support a cafe with two separate service windows in one day?
Yes. A split shift, such as a morning window and a separate evening window with a gap between, needs two distinct OpeningHoursSpecification blocks that share the same dayOfWeek value, each with its own opens and closes pair. One block spanning the full range would wrongly show the gap as open. Run the live page through a structured data validator after the change to confirm both blocks validate cleanly.
What time format does OpeningHoursSpecification use?
Times use a 24 hour format, and dayOfWeek takes schema.org's own defined day values rather than a freeform string. Exception blocks add validFrom and validThrough, which take dates marking the window they apply to. A cafe open past midnight must express its closes time as the following day's early hour, because a same day value earlier than opens reads as invalid or backward to a validator.
Should the hours on my website and my Google Business Profile match exactly?
Yes. Google Business Profile Help describes the profile as the place most customers see hours information first, so a mismatch between the profile and the site's schema markup is not just a technical inconsistency, it is the kind of gap that leads a customer to show up at the wrong time. Update both in the same sitting whenever hours change.
What is the most common hours markup error to check for first?
Check for a closes time that reads as earlier than the opens time because a past midnight close was not handled. A validator typically flags it as an invalid or unusual time range, and it is especially common at cafes with evening hours. After that, look for a missing closes property, overlapping blocks for the same day left from older edits, and special hours blocks whose validThrough date has already passed.
Does hours markup improve search rankings?
No such claim is supported by the documentation reviewed for this post. Structured data for hours is documented as helping search features display accurate hours, not as a ranking factor. What it does protect is accuracy: when OpeningHoursSpecification markup matches the hours on your Google Business Profile, which Google Business Profile Help describes as where most customers see hours first, fewer people arrive to a locked door.
How should a cafe closed on Mondays express that in schema?
Leave Monday out of every OpeningHoursSpecification block's dayOfWeek list. Do not add a Monday block with matching or zero length opens and closes times, which some site builders generate by default and which misstates the closure. Omitting the day is the correct way to express a closed day in schema.org markup. Then confirm your Google Business Profile also shows Monday as closed, so the site and profile agree.
How does a repeating annual holiday get handled in schema?
Neither schema.org's documentation nor Google's Business Profile Help pages, as reviewed for this post, state a fixed rule for this. Absent a documented rule, the safer practice is treating a repeating holiday exception as something to revisit and update with new validFrom and validThrough dates every year, rather than assuming a static block stays correct indefinitely.
What happens if an old holiday hours block is never removed?
It typically will not throw a hard validator error, since the block remains technically well formed, but a stale special hours block sitting past its intended window can quietly confuse anything reading the page later. Checking for and removing expired blocks belongs in a regular routine, since it is easy to forget after the urgency of the holiday has passed.
What tools can check whether an hours block is valid?
A standard structured data validator, run against the live page after any hours change, will confirm whether a block is well formed and flag common issues like a missing closes property or an invalid time range. Running the validator in the same sitting as any hours update is a simple, low effort habit worth building into a routine.
Work with Hillcane
Your cafe closes early for a holiday and Google keeps showing the old hours. The fix is a documented schema property, matched to what your profile actually says.
Want your holiday hours tested and synced across your profile and your site in one sitting? Reach Hillcane at hillcane.co/contact or call (256) 384-2449.



Comments