2.4Dates, regular expressions & emails
The essentials in 30 seconds
- Dates with DateTime: create, format for display (->format), compute gaps (->diff) — timezone configured, otherwise wrong times.
- Store ISO, display local: store YYYY-MM-DD (sortable, unambiguous) and format only for display — never a localized format in the database.
- filter_var first, regex if needed, library for emails: email and URL via filter_var, business patterns in tested regex, sending via PHPMailer or Symfony Mailer — never mail() in serious production.
Running mini-case — Riad dates, checks and writes to members
Final act of the project: Riad timestamps registrations (2.1) and photos (2.3) with DateTime — after losing an afternoon to shifted times because the timezone was not configured. He checks member numbers (precise internal format) with a tested regex, but validates emails with filter_var rather than a homemade regex. And for the newsletter, he drops mail() for a dedicated library that handles authentication and deliverability. The level 2 project is delivered: forms, sessions, files, dates.
Epilogue to thread 2.1-2.3: Riad's site handles dates, patterns and emails with the right tools.
1. Dates: DateTime, timezone and storage
Dates are everywhere (creation, events, durations) and modern PHP handles them OBJECT-ORIENTED with DateTime — and DateTimeImmutable, preferable because it cannot be modified: create (new DateTime('2026-07-25'), new DateTime() for now), FORMAT for display (->format('d/m/Y')), compute DIFFERENCES (->diff() between two dates, which handles leap years and uneven months), add/subtract durations. The old procedural functions (date(), time(), strtotime()) exist — you will meet them. Two cautions: the timezone (to configure explicitly, otherwise all times are shifted — Riad's trap #1; for international use, store in UTC and convert for display) and the storage format: store in the standard ISO format (YYYY-MM-DD, also the database one — sortable, unambiguous) and FORMAT only for display — never the reverse.
2. Regex: powerful, but not for everything
Regular expressions (regex) describe PATTERNS in text: checking a format (postal code, business reference), extracting parts, replacing by pattern. In PHP: preg_match() (does it match?), preg_match_all(), preg_replace(), preg_split(). They are POWERFUL but notoriously hard (cryptic syntax, easy to write badly — too strict, too lax, sometimes slow). Hence the rule: for COMMON validations (email, URL, IP, integer), do NOT write your regex — use filter_var() (FILTER_VALIDATE_EMAIL, FILTER_VALIDATE_URL), proven and reliable (correctly validating an email with regex is surprisingly hard). Reserve regex for SPECIFIC patterns not covered by filters — like Riad's member number — testing them carefully and commenting them. For SIMPLE handling (search, replace, split), prefer string functions, more readable and faster.
3. Emails: dedicated library, locked headers
The native mail() function is BASIC and unreliable (deliverability, spam, server configuration): to send seriously, use DEDICATED LIBRARIES (PHPMailer, Symfony Mailer) which handle SMTP authentication, attachments, HTML and deliverability — or third-party sending services. SECURITY point: never insert unchecked user data into email HEADERS (header injection flaw). So Riad's newsletter goes out via a library, with headers built from controlled values. Cross-cutting principle of this section: rely on PROVEN TOOLS (DateTime, filter_var, email libraries) rather than hand-crafting everything.
Did you know?
One line of timezone is worth an afternoon of debugging.
🔎 Concrete example — 🔎 Concrete example — Riad's registration, dated and validated
DateTime + filter_var, as with Riad:
$d = new DateTime('2026-07-25');
echo $d->format('d/m/Y');
// storage: ISO format 2026-07-25
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
}
Reading: date creation and formatting, ISO storage, reliable validation with no homemade regex.
Lesson: configure the timezone; store ISO, format for display.
Section vocabulary
- Dates (DateTime)
- Modern PHP handles dates with DateTime/DateTimeImmutable: create, FORMAT for display (->format), compute differences (->diff), add/subtract.
Simple example: Riad timestamps each registration with new DateTime().
What it is not: duration calculations by hand on timestamps.
Reflex: handling a date = DateTime.
- Timezone & storage format
- Configure the TIMEZONE (otherwise wrong times); STORE in the standard ISO format (YYYY-MM-DD) and FORMAT only for display.
Simple example: Riad stores 2026-07-25 and displays 25/07/2026.
What it is not: a localized date stored as-is in the database.
Reflex: store ISO, display local.
- Regular expressions (regex)
- A mini-language describing patterns in text; preg_match (does it match?), preg_replace (replace) — powerful but hard.
Simple example: Riad checks the member number with a tested regex.
What it is not: a homemade regex for validating emails.
Reflex: specific business pattern = tested and commented regex.
- filter_var for validating
- For common validations (email, URL…), prefer filter_var (FILTER_VALIDATE_EMAIL…) to a homemade regex — more reliable.
Simple example: Each member email goes through FILTER_VALIDATE_EMAIL.
What it is not: a regex copied without testing for emails.
Reflex: common case = proven filter.
- Sending emails
- The native mail() function is basic and unreliable; use dedicated libraries (PHPMailer, Symfony Mailer) — never unchecked data in headers.
Simple example: Riad's newsletter goes out via a library with authenticated SMTP.
What it is not: hand-crafted mail() for bulk sending.
Reflex: serious email = dedicated library.
In practice — In practice — Dates, regex and emails
- Handle dates with DateTime: create, format for display (->format('d/m/Y')), compute a difference (->diff); configure the timezone.
- Remember: store dates in the standard (ISO) format and FORMAT only for display (never store a localized format).
- Validate with filter_var (email, URL) rather than a homemade regex; reserve regex (preg_match) for specific patterns.
- To send email seriously, use a dedicated library (PHPMailer / Symfony Mailer) rather than mail(); never inject unchecked data into headers.
Ready-to-say script — defending proven tools in code review (30 s)
“For dates I use DateTime with the timezone configured, I store in ISO and format for display — no more shifted times. Emails go through filter_var, not a homemade regex. And sending goes via a library with authenticated SMTP, not mail(). Three proven tools, zero tinkering.”
Say it while showing the fixed timezone bug and a homemade regex replaced by filter_var.
Copy-paste template — date, gap and email validation
format('d/m/Y');
$aujourdhui = new DateTime();
$ecart = $inscription->diff($aujourdhui);
echo $ecart->format('%a days of membership');
// storage: $inscription->format('Y-m-d H:i:s')
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo 'Invalid email.';
}
Member seniority
Riad computes years of membership with ->diff: leap years and uneven months handled, no manual calculation.
Business reference
The member number follows a strict internal format: dedicated regex, tested on edge cases, commented.
Welcome email
Address validated by filter_var, sending via SMTP library: the welcome arrives instead of ending in spam.
Legacy procedural code
A date() or strtotime() met during maintenance: Riad reads it, then migrates to DateTime for any handling.
✓ Best practices
- Use DateTime/DateTimeImmutable; configure the timezone.
- Store dates in ISO, format only for display.
- Validate email/URL with filter_var; send emails via a dedicated library.
✗ Common pitfalls
- Timezone not configured (shifted times) — trap #1.
- Storing a date in localized format (ambiguous, unsortable).
- Writing a homemade regex to validate an email (hard, unreliable).
Key takeaways
- DATES: modern PHP uses the DateTime/DateTimeImmutable class (create, ->format for display, ->diff for differences). Caution: TIMEZONE (otherwise wrong times) and STORAGE FORMAT (ISO YYYY-MM-DD, format only for display).
- REGEX (preg_match, preg_replace) = patterns in text, powerful but hard. For COMMON validations (email, URL), prefer filter_var (FILTER_VALIDATE_EMAIL) to a homemade regex.
- EMAILS: the native mail() function is basic/unreliable; use DEDICATED LIBRARIES (PHPMailer, Symfony Mailer) for reliability and deliverability.
- Cross-cutting principle: rely on PROVEN TOOLS (DateTime, filter_var, email libraries) rather than doing everything by hand. Security: no unchecked data in email headers.
48h challenge — apply it tomorrow
Timestamp a real data item with DateTime (timezone configured, ISO storage, formatted display, a real ->diff), replace a homemade email regex with filter_var, and prepare sending via library instead of mail().
- I can handle a date (create, format, compute a gap) with no timezone error.
- I know when to use filter_var and when a regex is justified.
- I have prepared email sending via library, headers locked.
Frequently asked questions
What are the common traps with dates in PHP, and how do I avoid them?
(1) Badly configured TIMEZONE (trap #1): shifted times, wrong timestamps. Configure it explicitly (date_default_timezone_set() or date.timezone); for international use, store in UTC and convert for display. (2) Confusing STORAGE and DISPLAY: never store a localized format — store in ISO (YYYY-MM-DD, sortable, unambiguous, the database standard) and format ONLY for display (->format('d/m/Y')). (3) Old functions (date(), strtotime()): prefer DateTime/DateTimeImmutable for handling. (4) Manual calculations (subtracted timestamps): tricky (leap years, uneven months) — use ->diff(). (5) Daylight saving: DateTime with the right timezones handles it. (6) Parsing: DateTime::createFromFormat() with a precise format, then validation.
Should I learn regular expressions, and when should I use (or avoid) them?
Yes, the BASICS — you will meet them everywhere — but without obsession. AVOID: (1) COMMON validations (email, URL, IP, integer) — use filter_var (FILTER_VALIDATE_EMAIL…): validating an email with regex is surprisingly hard; homemade regexes are too strict or too lax; (2) SIMPLE handling — prefer string functions (str_contains, str_replace, explode). JUSTIFIED: SPECIFIC patterns not covered (internal format, business reference, precise extraction) — carefully tested (edge cases) and COMMENTED (unreadable otherwise). A good developer knows when to use a regex and when to prefer the dedicated tool — no regex everywhere out of bravado.
How do I validate an email in PHP?