Data Types, Strings, Dates and Operators · Lesson 6 of 41
Date, Datetime and Time in Apex
Close dates, SLAs, renewals, business hours, reports "for last month" – date logic is a big part of Salesforce development. Apex gives you three types: Date (a calendar day), Datetime (an exact moment) and Time (a time of day). Most bugs come from mixing them up or forgetting time zones, so that's where this lesson spends its time.
In this lesson
- Creating dates and doing date arithmetic
- Differences and period boundaries
- Datetime, GMT and the user's time zone
- Formatting and parsing
- Time values
Date: a calendar day
Date closeDate = Date.newInstance(2026, 1, 31); // year, month (1–12), day
System.debug('Close date: ' + closeDate);
System.debug('+10 days: ' + closeDate.addDays(10));
System.debug('+1 month: ' + closeDate.addMonths(1)); // clamps to the month's last day
System.debug('-1 year: ' + closeDate.addYears(-1));
System.debug('Month start: ' + closeDate.toStartOfMonth());
System.debug('Days in Feb 2028: ' + Date.daysInMonth(2028, 2));
System.debug('Leap year 2028? ' + Date.isLeapYear(2028));
System.debug('Year ' + closeDate.year() + ', month ' + closeDate.month() + ', day ' + closeDate.day());
System.debug('Day of year: ' + closeDate.dayOfYear());
Close date: 2026-01-31 00:00:00 +10 days: 2026-02-10 00:00:00 +1 month: 2026-02-28 00:00:00 -1 year: 2025-01-31 00:00:00 Month start: 2026-01-01 00:00:00 Days in Feb 2028: 29 Leap year 2028? true Year 2026, month 1, day 31 Day of year: 31
Two things to notice. Concatenating a Date into a String prints a midnight time (2026-01-31 00:00:00); use String.valueOf(d) for 2026-01-31 or d.format() for the user's locale. And addMonths(1) on 31 January gives the last day of February instead of overflowing into March – sensible behaviour for renewal dates. In real code you'll often start from Date.today(), which uses the running user's time zone to decide what "today" is.
Differences between dates
Date startDate = Date.newInstance(2026, 3, 15);
Date endDate = Date.newInstance(2026, 6, 10);
System.debug('daysBetween: ' + startDate.daysBetween(endDate));
System.debug('monthsBetween: ' + startDate.monthsBetween(endDate));
System.debug('Backwards: ' + endDate.daysBetween(startDate));
System.debug('Same day? ' + startDate.isSameDay(Date.newInstance(2026, 3, 15)));
System.debug('Before? ' + (startDate < endDate));
// a common question: how many days until renewal?
Date renewal = Date.newInstance(2026, 12, 1);
Date asOf = Date.newInstance(2026, 11, 8);
Integer daysLeft = asOf.daysBetween(renewal);
System.debug(daysLeft <= 30 ? 'Renewal due in ' + daysLeft + ' days' : 'Not yet');
daysBetween: 87 monthsBetween: 3 Backwards: -87 Same day? true Before? true Renewal due in 23 days
monthsBetween counts calendar-month boundaries and ignores the day of the month, so 15 March to 10 June is 3 even though it's less than three full months. For "full months" logic, compare days yourself.
Datetime: a moment, stored in GMT
A Datetime is an exact instant. Salesforce stores it in GMT (UTC) and shows it to each user in their own time zone. The methods come in pairs: plain ones use the running user's time zone, Gmt ones use UTC.
Datetime meeting = Datetime.newInstanceGmt(2026, 11, 8, 9, 30, 0); // 09:30 UTC
System.debug('GMT: ' + meeting.formatGmt('yyyy-MM-dd HH:mm'));
System.debug('India: ' + meeting.format('yyyy-MM-dd HH:mm z', 'Asia/Kolkata'));
System.debug('New York: ' + meeting.format('yyyy-MM-dd hh:mm a z', 'America/New_York'));
System.debug('Berlin: ' + meeting.format('EEE d MMM yyyy, HH:mm', 'Europe/Berlin'));
System.debug('+90 minutes (GMT): ' + meeting.addMinutes(90).formatGmt('HH:mm'));
System.debug('Epoch ms: ' + meeting.getTime());
System.debug('Date part in GMT: ' + meeting.dateGmt());
// difference in hours between two Datetimes
Datetime opened = Datetime.newInstanceGmt(2026, 11, 7, 22, 0, 0);
Decimal hours = Decimal.valueOf(meeting.getTime() - opened.getTime()) / (1000 * 60 * 60);
System.debug('Hours open: ' + hours.setScale(1));
GMT: 2026-11-08 09:30 India: 2026-11-08 15:00 IST New York: 2026-11-08 04:30 AM EST Berlin: Sun 8 Nov 2026, 10:30 +90 minutes (GMT): 11:00 Epoch ms: 1794130200000 Date part in GMT: 2026-11-08 00:00:00 Hours open: 11.5
date() vs dateGmt()
myDatetime.date() returns the calendar date in the running user's time zone, dateGmt() in UTC. A case created at 22:00 UTC is "tomorrow" for a user in India. Decide deliberately which one your business rule means – and remember scheduled and batch jobs run as a specific user with their own time zone.
Formatting and parsing
Date d = Date.newInstance(2026, 11, 8);
System.debug('Date.format() (user locale): ' + d.format());
System.debug('String.valueOf (ISO): ' + String.valueOf(d));
Datetime dt = Datetime.newInstanceGmt(2026, 11, 8, 14, 5, 0);
System.debug('ISO 8601 for APIs: ' + dt.formatGmt('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
// parsing
Date parsed = Date.valueOf('2026-12-25'); // ISO yyyy-MM-dd
Datetime fromJson = (Datetime) JSON.deserialize('"2026-11-08T09:30:00.000Z"', Datetime.class);
System.debug(parsed + ' | ' + fromJson.formatGmt('dd MMM yyyy HH:mm'));
Date.format() (user locale): 08/11/2026 String.valueOf (ISO): 2026-11-08 ISO 8601 for APIs: 2026-11-08T14:05:00Z 2026-12-25 00:00:00 | 08 Nov 2026 09:30
| Pattern | Meaning | Example |
|---|---|---|
yyyy / MM / dd | year / month / day | 2026 / 11 / 08 |
MMM / EEE | short month / weekday name | Nov / Sun |
HH / hh + a | 24-hour / 12-hour + AM/PM | 14 / 02 PM |
mm / ss | minutes / seconds | 05 / 00 |
z | time zone abbreviation | IST |
Watch the case: MM is months, mm is minutes – mixing them up is a classic bug.
Time: a time of day
Time opens = Time.newInstance(9, 0, 0, 0);
Time closes = Time.newInstance(18, 30, 0, 0);
Time callAt = Time.newInstance(17, 45, 0, 0);
System.debug('Open: ' + opens + ', closes: ' + closes);
System.debug('Within hours? ' + (callAt >= opens && callAt < closes));
System.debug('Last call slot: ' + closes.addMinutes(-30));
// combine a Date and a Time into a Datetime (in the user's time zone)
Datetime appointment = Datetime.newInstance(Date.newInstance(2026, 11, 9), opens);
System.debug('Appointment hour (user TZ): ' + appointment.hour());
Open: 09:00:00.000Z, closes: 18:30:00.000Z Within hours? true Last call slot: 18:00:00.000Z Appointment hour (user TZ): 9
For real working hours with holidays, use the BusinessHours class (BusinessHours.add(), BusinessHours.isWithin()) with the business hours your admin configured, rather than hard-coding times.
Practice
Try it yourself
Write an expression for the last day of the current month.
Show answer
Date.today().toStartOfMonth().addMonths(1).addDays(-1)– orDate.newInstance(y, m, Date.daysInMonth(y, m)).An integration needs timestamps like 2026-11-08T09:30:00Z. Which method do you use, and why not
format()?Show answer
formatGmt('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'').format()converts to the running user's time zone, which would make the "Z" (UTC) label wrong.
Quick check
In which time zone does Salesforce store Datetime values?
GMT (UTC).
What does Date.newInstance(2026, 1, 31).addMonths(1) return?
2026-02-28 – it clamps to the last day of the month.
What's the difference between MM and mm in a format pattern?
MM is month, mm is minutes.
Summary
- Date is a calendar day, Datetime an exact moment in GMT, Time a time of day.
- Use addDays/addMonths, daysBetween and toStartOfMonth for date arithmetic.
- Plain Datetime methods use the running user's time zone; Gmt methods use UTC.
- Format with explicit patterns and time zones; use BusinessHours for working time.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.