Formatting, Parsing and Time Zones in Java
DateTimeFormatter converts between text and date time values, and ZoneId handles the region rules that make time zones difficult.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Formatting
LocalDateTime moment = LocalDateTime.of(2026, 8, 21, 14, 5, 30);
System.out.println(moment); // 2026-08-21T14:05:30
System.out.println(moment.format(DateTimeFormatter.ISO_LOCAL_DATE));
DateTimeFormatter readable = DateTimeFormatter.ofPattern("dd MMM yyyy, HH:mm");
System.out.println(moment.format(readable)); // 21 Aug 2026, 14:05
DateTimeFormatter localised = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.MEDIUM)
.withLocale(Locale.UK);
System.out.println(moment.format(localised));Pattern letters
| Letter | Meaning | Example |
|---|---|---|
yyyy | Year | 2026 |
MM / MMM / MMMM | Month number, short name, full name | 08 / Aug / August |
dd | Day of month | 21 |
EEE / EEEE | Day of week | Fri / Friday |
HH | Hour, 0 to 23 | 14 |
hh with a | Hour, 1 to 12, with AM or PM | 02 PM |
mm | Minute | 05 |
ss | Second | 30 |
SSS | Millisecond | 123 |
z / VV | Zone name / zone id | IST / Asia/Kolkata |
XXX | Offset | +05:30 |
Case matters.MMis the month andmmis the minute;HHis a 24 hour clock andhhneeds an AM or PM marker to be meaningful. Mixing them up is the most common formatting bug in Java.
Parsing
LocalDate iso = LocalDate.parse("2026-08-21"); // ISO needs no formatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate custom = LocalDate.parse("21/08/2026", formatter);
try {
LocalDate bad = LocalDate.parse("31/02/2026", formatter);
} catch (DateTimeParseException e) {
System.out.println("Invalid date: " + e.getParsedString());
}Parsing throws DateTimeParseException for anything that does not match, including dates that look well formed but do not exist. Always handle it at an input boundary.
Formatters are immutable and thread safe
public class Formats {
public static final DateTimeFormatter DISPLAY_DATE =
DateTimeFormatter.ofPattern("dd MMM yyyy");
}This was impossible with SimpleDateFormat, which was mutable and had to be created per call or held in a thread local. A DateTimeFormatter can safely be a shared constant.
Time zones
ZoneId kolkata = ZoneId.of("Asia/Kolkata");
ZoneId london = ZoneId.of("Europe/London");
ZoneId systemZone = ZoneId.systemDefault();
ZoneOffset fixed = ZoneOffset.of("+05:30");
System.out.println(ZoneId.getAvailableZoneIds().size());ZoneId | ZoneOffset | |
|---|---|---|
| Represents | A region with rules | A fixed difference from UTC |
| Handles daylight saving | Yes | No |
| Example | Europe/London | +05:30 |
| Use for | Future events, user preferences | Stored timestamps |
Always use a region identifier such as Asia/Kolkata for a zone. Three letter abbreviations are ambiguous, and a fixed offset silently ignores daylight saving.
ZonedDateTime
ZonedDateTime here = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
ZonedDateTime there = here.withZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println(here); // e.g. 2026-08-21T19:35+05:30[Asia/Kolkata]
System.out.println(there); // the same moment, shown in LondonZonedDateTime sameLocal = here.withZoneSameLocal(ZoneId.of("Europe/London"));
// a DIFFERENT moment, keeping the clock readingwithZoneSameInstantkeeps the moment and changes the reading;withZoneSameLocalkeeps the reading and changes the moment. Choosing the wrong one is a classic source of scheduling bugs.
Daylight saving edge cases
ZoneId london = ZoneId.of("Europe/London");
// A local time that does not exist: the clock jumps forward
ZonedDateTime gap = ZonedDateTime.of(
LocalDateTime.of(2026, 3, 29, 1, 30), london); // shifted forward automatically
// A local time that occurs twice: the clock goes back
ZonedDateTime overlap = ZonedDateTime.of(
LocalDateTime.of(2026, 10, 25, 1, 30), london); // the earlier one is chosen
ZonedDateTime later = overlap.withLaterOffsetAtOverlap();The API resolves both cases with defined rules rather than throwing, and gives explicit methods when the other choice is wanted.
Storing and displaying
// Store in UTC
Instant storedAt = Instant.now();
// Display in the user zone
String display = storedAt
.atZone(user.zone())
.format(DateTimeFormatter.ofPattern("dd MMM yyyy, HH:mm z"));Store the instant, convert for display. Storing a local time plus a zone name is only correct for future events whose zone rules might change, such as a recurring meeting.
Working with a database
// JDBC 4.2 maps java.time types directly
statement.setObject(1, LocalDate.of(2026, 8, 21)); // DATE
statement.setObject(2, Instant.now().atOffset(ZoneOffset.UTC)); // TIMESTAMP WITH TIME ZONE
LocalDate date = resultSet.getObject("published_on", LocalDate.class);
OffsetDateTime created = resultSet.getObject("created_at", OffsetDateTime.class);Common mistakes
- Writing
mmfor the month orMMfor the minute. - Using
hhwithout an AM or PM marker. - Using a three letter zone abbreviation such as
IST, which is ambiguous. - Confusing
withZoneSameInstantwithwithZoneSameLocal. - Storing local times without a zone and losing the meaning.
- Sharing a
SimpleDateFormatbetween threads, which corrupts output. - Ignoring the locale, so month names appear in an unexpected language.
Best practices
- Keep formatters as static final constants.
- Use ISO formats for anything machine readable, and patterns only for display.
- Use region based zone identifiers.
- Store timestamps in UTC and convert at the edge.
- Set the locale explicitly when the output includes names.
- Handle
DateTimeParseExceptionwherever text becomes a date.
Practice
- Format the current date and time as
Friday, 21 August 2026 at 02:05 PM. - Parse
21-08-2026and handle an invalid input such as31-02-2026. - Convert a meeting at 09:00 in Kolkata into the equivalent London time.
- Explain the difference between
withZoneSameInstantandwithZoneSameLocalwith a worked example. - Why is storing
LocalDateTimefor a created timestamp a poor choice?
Conclusion
Use DateTimeFormatter constants for text conversion and region based ZoneId values for zones. Store instants in UTC, convert only when displaying, and be deliberate about whether you are preserving the moment or the clock reading.