Java SimpleDateFormat Tester — Common Patterns & ExamplesJava’s java.text.SimpleDateFormat is a widely used class for formatting and parsing dates. Although Java 8 introduced the newer java.time API (recommended for new projects), SimpleDateFormat remains common in legacy code and quick utilities like small “tester” tools. This article explains how SimpleDateFormat works, lists common patterns, describes pitfalls (including thread-safety), and provides examples — including a small tester you can use or adapt.
What SimpleDateFormat does
SimpleDateFormat formats Date objects into strings and parses strings back into Date objects according to a pattern you specify. Patterns use letters where each letter represents a date/time field (year, month, day, hour, minute, second, time zone, etc.).
Pattern letters — the essentials
Common pattern letters (most used ones):
- y — year (yy = two digits, yyyy = four digits)
- M — month in year (MM = two-digit month, MMM = short name, MMMM = full name)
- d — day in month (dd = two digits)
- H — hour in day (0-23)
- h — hour in am/pm (1-12)
- m — minute in hour
- s — second in minute
- S — millisecond
- E — day name in week (EEE = short, EEEE = full)
- a — am/pm marker
- z / Z / X — time zone designators
Literals can be quoted with single quotes (‘).
Common example patterns
- “yyyy-MM-dd” — ISO-like date (e.g., 2025-09-01)
- “dd/MM/yyyy” — common European format (e.g., 01/09/2025)
- “MM/dd/yyyy” — common US format (e.g., 09/01/2025)
- “yyyy-MM-dd HH:mm:ss” — date and time (24-hour) (e.g., 2025-09-01 13:45:30)
- “yyyy-MM-dd’T’HH:mm:ss.SSSZ” — ISO 8601-ish with timezone offset (e.g., 2025-09-01T13:45:30.123+0200)
- “EEE, MMM d, “yy” — compact textual (e.g., Mon, Sep 1, ‘25)
- “h:mm a” — 12-hour time with AM/PM (e.g., 1:45 PM)
Parsing vs. formatting
- Formatting: convert Date -> String using format(Date).
- Parsing: convert String -> Date using parse(String). Parsing is lenient by default: “32 Jan 2025” may roll over into February. Use setLenient(false) to enforce strict parsing.
Example: strict parsing
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); sdf.setLenient(false); Date d = sdf.parse("31/02/2025"); // throws ParseException
Thread-safety — a common pitfall
SimpleDateFormat is not thread-safe. Reusing one instance across threads can cause incorrect results or exceptions. Solutions:
- Create a new SimpleDateFormat per use (cheap for most apps).
- Use ThreadLocal
to reuse per-thread instances. - Synchronize access (works but may hurt performance).
- Prefer java.time.format.DateTimeFormatter (thread-safe) in Java 8+.
Example ThreadLocal:
private static final ThreadLocal<SimpleDateFormat> TL_SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public static String format(Date date) { return TL_SDF.get().format(date); }
Time zones and locales
SimpleDateFormat uses the default locale and default time zone unless you specify otherwise. To format for a specific locale or time zone:
SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy HH:mm", Locale.UK); sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Be explicit when your application serves users in different locales or when storing/reading timestamps.
Building a simple tester (CLI & web examples)
Below are two minimal tester examples you can adapt: a command-line tester and a simple web servlet.
CLI tester (reads pattern and date string, prints parse/format):
import java.text.*; import java.util.*; public class SdfTester { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); System.out.print("Enter pattern: "); String pattern = sc.nextLine(); System.out.print("Enter date string to parse (or blank to format current date): "); String input = sc.nextLine(); SimpleDateFormat sdf = new SimpleDateFormat(pattern); sdf.setLenient(false); if (input.trim().isEmpty()) { System.out.println("Formatted now: " + sdf.format(new Date())); } else { try { Date d = sdf.parse(input); System.out.println("Parsed date (UTC epoch ms): " + d.getTime()); System.out.println("Reformatted: " + sdf.format(d)); } catch (ParseException e) { System.out.println("Parse error: " + e.getMessage()); } } } }
Simple servlet snippet (for a quick web tester):
// inside doGet/doPost String pattern = request.getParameter("pattern"); String input = request.getParameter("input"); SimpleDateFormat sdf = new SimpleDateFormat(pattern); sdf.setLenient(false); response.setContentType("text/plain; charset=UTF-8"); try { if (input == null || input.isEmpty()) { response.getWriter().println("Formatted now: " + sdf.format(new Date())); } else { Date d = sdf.parse(input); response.getWriter().println("Parsed: " + d + " (ms=" + d.getTime() + ")"); } } catch (ParseException e) { response.getWriter().println("Parse error: " + e.getMessage()); }
Examples and edge cases
- Two-digit year (“yy”): “25” becomes 2025 using a pivot year algorithm; ambiguous for historic dates.
- Month names depend on Locale: “MMM” with Locale.FRANCE returns “janv.” for January.
- Time zone parsing: “Z” parses +0200, “X” parses ISO 8601 offsets like +02:00.
- Lenient parsing: “2000-02-29” parsed on a non-leap-year pattern may behave unexpectedly if lenient.
When to prefer java.time
For new code prefer java.time:
- DateTimeFormatter is immutable and thread-safe.
- Clearer types: LocalDate, LocalDateTime, ZonedDateTime.
- Better ISO 8601 support and parsing.
Quick replacement example:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime dt = LocalDateTime.parse("2025-09-01 13:45:30", fmt); String out = dt.format(fmt);
Summary — best practices
- Use java.time (DateTimeFormatter) for new projects.
- If using SimpleDateFormat: create per-use instances or use ThreadLocal; set lenient=false for strict parsing; specify Locale and TimeZone when needed.
- Test patterns with a small tester (CLI or web) to ensure parsing and formatting behave as expected.
Leave a Reply