
Reviews
Spreadsheet or API: where the calendar should actually live
Spreadsheet or service: where the calendar lives, the contract a date must satisfy crossing between them, and one test set both sides should pass alike.
What to take away
A spreadsheet and a service both compute dates correctly. Where the calendar should actually live depends on who changes it and how the answer must be reproduced. The spreadsheet side includes Google Sheets, Microsoft Excel, Airtable, Smartsheet and Notion. The service side includes Google Calendar API, Microsoft Graph, Nager.Date, Workalendar and Calendarific.
The choice also depends on how a date travels between them without shifting by a day.
This page runs no calculation and recommends no product. It explains what each kind of tool is for and the contract a date must meet to cross between them. It also gives a request shape carrying everything a working-day service needs, plus one test set both tools should pass identically.
- Spreadsheets such as Google Sheets, Microsoft Excel, Airtable, Smartsheet and Notion store a date as a count or text field. Services such as Google Calendar API, Microsoft Graph, Nager.Date, Workalendar and Calendarific return dates as text or typed values. Every conversion between them is a place to lose a day.
- The closure list is the only part of the calculation with any judgment in it. Keep one copy, version it, and load the same version into both tools.
- Run the same test cases through both. A difference is never a bug in the arithmetic; it is a convention one tool applied and the other did not.
What each tool is for
Spreadsheet
- One person needs one date, now
- Yes
- Several people need the same kind of date
- Fragile: each copy drifts
- The date belongs to a record in another system
- No
- The answer must be reproduced months later
- Only if the closure tab is versioned
- The calendar changes often
- Every workbook must be updated
- Someone must be able to audit the working
- Yes: the formula is visible
Service
- One person needs one date, now
- Overkill
- Several people need the same kind of date
- Yes: one calendar, one convention
- The date belongs to a record in another system
- Yes
- The answer must be reproduced months later
- Yes, if the calendar is versioned
- The calendar changes often
- One place to update
- Someone must be able to audit the working
- Only if the response echoes its inputs
The middle rows are where most organizations sit. Five people computing the same kind of deadline in five workbooks will not produce five identical calendars for long.
Spreadsheet vs service
Spreadsheet
- One person, one date
- Yes
- Several people, same date
- Copies drift
- Date in another record
- No
- Reproduced months later
- If versioned
- Calendar changes often
- Update each workbook
- Audit the working
- Formula visible
Service
- One person, one date
- Overkill
- Several people, same date
- One convention
- Date in another record
- Yes
- Reproduced months later
- If versioned
- Calendar changes often
- One place
- Audit the working
- If inputs echoed
Named tools make the split concrete. Google Sheets and Microsoft Excel keep the formula in the cell, so the working is visible. Airtable, Smartsheet and Notion store dates in a shared record, which helps when several people touch the same calendar.
Google Calendar API and Microsoft Graph tie dates to events and accounts. Nager.Date, Workalendar and Calendarific supply holiday data.
| Tool | Type | Date storage | Audit and versioning | Typical cost | Fits |
|---|---|---|---|---|---|
| Google Sheets | Spreadsheet | Serial day count from 1899-12-30 | Cell formula visible; version history | Free with a Google account | One person who needs a visible formula |
| Microsoft Excel | Spreadsheet | Serial day count from 1900-01-01 on the default 1900 system | Cell formula visible; workbook versions | Microsoft 365 subscription or one-time license | Desktop work and large workbooks |
| Airtable | Spreadsheet-database | Date fields stored as ISO 8601 text | Revision history on paid plans | Free plan; Team listed at $20 per user per month billed annually | Teams that need linked records |
| Smartsheet | Work management | Date columns typed as dates or text | Activity log on paid plans | Paid plans start around $9 per user per month | Teams with approvals and shared sheets |
| Notion | Database and docs | Date property stores ISO 8601 | Page history | Free for individuals; Plus $10 per member per month billed annually | Lightweight shared calendars |
| Google Calendar API | Service | RFC 3339 date or dateTime | Google Workspace audit logs | Free with a Google account, with quota limits | Dates tied to events and invites |
| Microsoft Graph | Service | ISO 8601 dateTimeTimeZone | Microsoft 365 audit logs | Included with Microsoft 365 | Microsoft 365 records |
| Nager.Date | Service | YYYY-MM-DD holiday dates | No built-in audit trail | Free, no API key | Public holiday lookups |
| Workalendar | Python library | Python date objects | Code version control | Free and open source | Local holiday calculation |
| Calendarific | Service | ISO 8601 dates | API request logs | Free tier with a request cap, then paid plans | Holiday data through an API key |
The contract a date has to satisfy
Both sides count from an origin, and the origins differ. A sheet counts days from a date near the end of 1899; a service that speaks in seconds usually counts from the start of 1970, which is Unix time.
Two origins, two types
Spreadsheet
- Origin
- 1899-12-30
- A date is
- Integer 46388
- A moment is
- Fraction after it
- Difference is
- Integer days
- Boundary risk
- Serial read as number
Service
- Origin
- 1970-01-01
- A date is
- Text 2027-03-01
- A moment is
- Text with offset
- Difference is
- Duration
- Boundary risk
- UTC shifts the day
Neither number means anything without its origin. That is the argument for sending text in ISO 8601 order instead of either.
In a spreadsheet a date is an integer counting days from an origin. A time is the fraction after it.
On the common 1900 system, 1 January 2027 is 46388 and 1 March 2027 is 46447. =B2-A2 works because subtracting two integers subtracts two dates. A cell showing a date and a cell showing 46447 hold the same value in different clothes.
In a service a date is text: 2027-03-01. A moment is text with a time and an offset: 2027-03-05T23:30:00-05:00. The two are different types, and a difference between two dates is an integer while a difference between two moments is a duration.
Crossing the boundary is where days go missing:
- A moment sent where a date was expected.
2027-03-05T23:30:00-05:00is 06 March at 04:30 in UTC. A service that reads the date part after converting to UTC has moved your Friday to Saturday. - A serial sent where text was expected. 46447 is a number to a service, not a date, and a lenient parser may read it as something else entirely.
- A date read as a moment at local midnight, then shifted. Local midnight in one zone is the previous evening in another, and the date part changes with it.
- Month numbering. Some programming interfaces count months from zero, so a date assembled from numbers can silently land a month early. Check the convention of the library you are using before you build a date from parts.
The rule that survives all of these: send date-only values as YYYY-MM-DD text, never as a moment and never as a serial, and convert to a serial only inside the spreadsheet with DATEVALUE or DATE.
A request that carries everything
Google Calendar API and Microsoft Graph both accept ISO 8601 values. Nager.Date and Calendarific return holiday dates as text. Workalendar computes them inside a Python process. A request that carries its assumptions lets any of them be swapped without changing the answer.
Request that carries its assumptions
- Send operation, start, count or end
- Send weekend string and calendar version
- Send start_counts and end_counts
- Receive result and skipped dates
- Receive applied block echoing inputs
- Recheck the arithmetic months later
A working-day service that cannot be argued with takes all of its assumptions as inputs and returns them with the answer. Vendor-neutral shape:
request
operation: add | subtract | between | hours
start: 2027-03-01
count or end: 10 (or an end date, for between)
weekend: "0000011" (Monday through Sunday, 1 = rest day)
calendar: { id: "court-x", version: "2026-09" }
start_counts: false
end_counts: true
zone: "Europe/Paris" (for hour operations only)
response
result: 2027-03-15
skipped: [ ...the dates that were passed over... ]
applied: { weekend, calendar, start_counts, end_counts }
computed_at: 2026-09-10T08:12:44Z
The applied block is what makes the answer defensible. Six months later the question is usually what it was computed with, and a response that echoes its inputs answers that in one line. The skipped list is what lets a human check the arithmetic in seconds.
The spreadsheet equivalent of this shape is a row with the same columns and the formula in the last one. If the row does not carry the weekend string and the calendar version, it is a result with no working.
One calendar, both tools
The closure list is a table with three columns: calendar id, date, and a note naming the publisher and date. Keep exactly one such table.
The spreadsheet loads it as a named range. The service loads it from the same file or source. Give each revision a version, and put that version beside every computed date in both tools.
No calendar is supplied here. Which publisher's list governs a deadline, and why the answer is a body rather than a country, is under adding business days; hour inputs for hours are under working hours.
The same tests in both tools
Run these through the spreadsheet formula and the service, with the same weekend string and no closures except where stated. All dates are in March 2027. Any difference is a convention to reconcile, not a defect to fix. The service can be Google Calendar API, Microsoft Graph, Nager.Date, Workalendar or Calendarific.
Nine cases both tools must pass
- Add 1 to Friday 5thMonday 8th
- Add 1 to Saturday 6thMonday 8th
- Add 0 to Saturday 6thunchanged
- Add 5 to Monday 1stMonday 8th
- Subtract 5 from Monday 8thMonday 1st
- Count 1st to 8th, both ends6
- Add 10 with 10th closedTuesday 16th
| Case | Expected |
|---|---|
| Add 1 to Friday 5th | Monday 8th |
| Add 1 to Saturday 6th | Monday 8th |
| Add 0 to Saturday 6th | Saturday 6th, unchanged |
| Add 5 to Monday 1st | Monday 8th |
| Subtract 5 from Monday 8th | Monday 1st |
| Subtract 1 from Sunday 7th | Friday 5th |
| Count working days Monday 1st to Monday 8th, both ends in | 6 |
| Add 10 to Monday 1st with Wednesday 10th closed | Tuesday 16th |
| Count Wednesday 10th to Wednesday 10th with the 10th closed | 0 |
The seventh row is the one that reconciles most disputes. A step of five working days and a set of six working days describe the same span, and a tool that reports the step as five while another reports the set as six has not disagreed about anything.
When to move from the sheet to the service
Move when more than one person computes the same kind of date, or the date must land in another system's record. Move when someone can challenge a date and get the working back, or the calendar changes more often than every workbook updates.
Until then the spreadsheet is the better tool. Its working is visible, and the tests above take a minute to run. Google Sheets and Microsoft Excel cover most single-team cases. Airtable, Smartsheet and Notion add shared records.
Forward and backward counts are different operations. Each fails in its own way. The backward count, where a wrong closure costs a day you thought you had, is under subtracting business days.
Both tools must agree on endpoint conventions. Those are under counting between two dates.
Common questions
Can I call a service from a spreadsheet cell?
Usually, and it is often the worst of both: a formula that depends on a network call, with the calendar in one place and the working in another. Google Sheets can call APIs through Apps Script; Excel can through Power Query or Office Scripts.
If you do it, have the cell store the applied block alongside the result, rather than the date alone.
Should the service or the client decide the convention?
The request should state it and the response should echo it. A service with a silent default is a service whose answers cannot be compared with anything else.
How do I version a closure list?
A date stamp on the file and a version column in the table. Every computed date carries the version it used. When the list changes, old dates keep their old version and are not recomputed unless someone decides to.
Is a moment ever the right thing to send for a date?
Only for hour operations, where the instant matters. For day operations, a moment is a date with an extra way to be wrong, as monthly working-day counts show when a month boundary sits at local midnight.
In this guide
- Spreadsheet and API tools formula testing for paritySpreadsheet and API tools formula parity: Excel NETWORKDAYS against Nager.Date, four pinned conventions, sixteen boundary cases, and the 1900/1904 epoch trap.
- Excel date serials and the 1900 and 1904 epochs: spreadsheet and API date handlingExcel date serials, the 1900 and 1904 epochs, text date traps, and spreadsheet and API tools that move dates without shifting or corrupting them.
- Spreadsheet and API Tools: Google Sheets Date SettingsSpreadsheet and API tools in Google Sheets can reinterpret dates through file time zone and locale settings, serial values, and API response formatting.
- Spreadsheet and API tools holidays 2027: criteria, no datesSpreadsheet and API tools holidays 2027: criteria for one closure list in Excel, Sheets, and Nager.Date, versioning it, and why no dates are listed here.
- Spreadsheet Date and Workday Counting MistakesSpreadsheet date and workday-count mistakes: text dates, locale-dependent imports, midnight shifts, serial values, endpoint rules, and closure ranges.







