- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Date and Deadline Mistakes That Quietly Break Business Apps
About Post
Date bugs are polite. They don't crash your app on the day you ship. They wait.
They wait for the 31st of the month, for the 29th of February, for a user in a different time zone, for the last day of a contract at ten in the morning. Then they quietly produce a wrong date, which turns into a wrong invoice, a wrong reminder, or a tenant being told their lease has expired when it hasn't.
Business apps (contracts, rent, subscriptions, HR leave, SLAs) are full of dates, so they're full of these. Here are the seven I see most often, why each happens, and the fix. Examples use Carbon in Laravel 12, but the traps are the same in any language.
1. Treating an end date like a timestamp
The mistake: a one-year contract runs from 1 January to 31 December. It's stored as ends_at = 2026-12-31 00:00:00 and checked with $contract->ends_at->isPast().
Why it breaks: on 31 December at 10am, midnight on the 31st is already in the past. The contract shows as expired for its entire last day.
The fix: business dates are dates, not moments. Store them in a date column, compare them to today's date, and decide once whether the end is inclusive or exclusive. Then name the column so nobody has to guess:
// Inclusive end date: the contract is active ON ends_on
Contract::whereDate('starts_on', '<=', today())
->whereDate('ends_on', '>=', today())
->get();
// One-year contract from 2026-01-01 ends on 2026-12-31
// (immutable dates assumed, see mistake 6)
$endsOn = $startsOn->addYear()->subDay();
Some teams prefer an exclusive end (ends_before = 2027-01-01, compared with <), which makes durations easier to calculate. Either works. Mixing both in one codebase is what causes the off-by-one.
2. Adding months and trusting the result
The mistake: a contract starts on 31 January, and the next rent due date is $start->addMonth().
Why it breaks: there is no 31 February. By default, Carbon (like PHP's DateTime) overflows into the next month, so you get 3 March in a normal year.
$start = CarbonImmutable::parse('2026-01-31');
$start->addMonth(); // 2026-03-03 (overflow)
$start->addMonthNoOverflow(); // 2026-02-28 (clamped to month end)
The fix: use the NoOverflow variants for anything a human calls "monthly". And watch the sneaky second half of this bug, next.
3. Chaining from the previous due date
The mistake: each due date is calculated from the previous one: Jan 31, then Feb 28, then Mar 28, then Apr 28...
Why it breaks: once the date gets clamped to the 28th, it stays there. The schedule drifts away from the 31st forever.
The fix: always calculate from the original anchor date, not the previous result:
$dueDates = collect(range(0, 11))->map(
fn (int $n) => $start->addMonthsNoOverflow($n)
);
// Jan 31, Feb 28, Mar 31, Apr 30, May 31, ...
4. Forgetting that years aren't 365 days
The mistake: "one year later" written as addDays(365), or annual renewals for a contract that starts on 29 February.
Why it breaks: a leap year has 366 days, so addDays(365) lands a day early. And a 29 February start has no matching date in most years. CarbonImmutable::parse('2028-02-29')->addYear() overflows to 1 March 2029, while addYearNoOverflow() gives 28 February.
The fix: add calendar units (addYear, addMonths), not day counts, and decide explicitly what a 29 February anniversary means for your business. Then write a test for it, because it only comes around every four years.
5. Asking "what day is it?" in the wrong time zone
The mistake: the server runs in UTC, the business runs in Doha (UTC+3). A payment deadline is "end of day, 5 October", checked with now()->toDateString().
Why it breaks: for the first three hours of every Doha day, the server still thinks it's yesterday. Late fees, reminders and "due today" lists are all shifted at the edges.
The fix: store timestamps in UTC, but compute business dates in the business's time zone, explicitly:
$businessToday = now('Asia/Qatar')->toDateString();
// Scheduled jobs too: run at 8am local time, not 8am UTC
Schedule::command('reminders:send')
->dailyAt('08:00')
->timezone('Asia/Qatar');
If your users span several time zones, a deadline needs a time zone attached to it. "5 October" alone isn't a moment in time.
6. Mutating a date you meant to keep
The mistake:
$start = $contract->starts_on; // a mutable Carbon instance
$end = $start->addYear(); // surprise: $start changed too
Why it breaks: by default, Laravel's date casts give you mutable Carbon objects. addYear() changes the object itself and returns it, so $start and $end are now the same date. Any code that uses $start afterwards is wrong.
The fix: use immutable dates everywhere. One line in a service provider's boot() method does it app-wide:
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
Date::use(CarbonImmutable::class);
Every method now returns a new instance and leaves the original alone. (For a single spot, $start->copy()->addYear() works too.)
7. Trusting diffInDays() to return a neat integer
The mistake: showing "days left" as now()->diffInDays($contract->ends_on).
Why it breaks: since Carbon 3 (which Laravel 12 uses), diffIn* methods return a float and are signed by default. Comparing now() (2:30pm) to a date at midnight gives something like 29.4, and a date in the past gives a negative number. Code written for Carbon 2, which returned absolute integers, now shows odd values.
The fix: compare dates to dates, and say what you mean:
$daysLeft = (int) today()->diffInDays($contract->ends_on->startOfDay());
// positive: days remaining; 0: ends today; negative: already ended
And remember the counting question: from 1 to 10 October is a difference of 9 days, but 10 days if both ends count. Contracts, leave requests and SLAs often mean the inclusive version.
The date rule: decide your conventions once (date vs timestamp, inclusive vs exclusive end, which time zone defines "today", immutable everywhere), write them down, and test the edges: the 31st, 29 February, midnight, and the last day of a contract.
A tiny test suite worth having
Date bugs are easy to test once you freeze time. In Laravel, $this->travelTo(...) lets a test pretend it's 31 December at 10am, or 1am in Doha. A handful of tests for the edge dates above will catch most of these before a customer does.
Which date bug has caught you? I'd bet on month-end overflow, but time zones are a strong second.

Be first to comment it...