Question
Java Date Parsing and Time Zone Transitions: Why 1 Second Becomes 353 Seconds
Question
In Java, why does subtracting two parsed times that appear to be 1 second apart produce 353 instead of 1?
Consider this code:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str3 = "1927-12-31 23:54:07";
String str4 = "1927-12-31 23:54:08";
Date sDt3 = sf.parse(str3);
Date sDt4 = sf.parse(str4);
long ld3 = sDt3.getTime() / 1000;
long ld4 = sDt4.getTime() / 1000;
System.out.println(ld4 - ld3);
}
}
The output is:
353
I would expect the difference to be 1, because the two date-time strings are only one second apart.
If the values are changed to:
String str3 = "1927-12-31 23:54:08";
String str4 = "1927-12-31 23:54:09";
then the result becomes 1.
Environment details:
Java version: 1.6.0_22
Time zone: Asia/Shanghai
Locale: zh_CN
What causes this behavior, and how is it related to Java date parsing, epoch milliseconds, and historical time zone rules?
Short Answer
By the end of this page, you will understand why old date-time values can behave unexpectedly in Java when parsed in a specific time zone. You will learn that Date stores an absolute instant, SimpleDateFormat uses the JVM default time zone unless told otherwise, and historical time zone transitions can make two local clock times that look 1 second apart map to instants that are hundreds of seconds apart.
Concept
Java date/time bugs often come from mixing up local date-time text with an absolute instant in time.
In this example, the strings:
1927-12-31 23:54:07
1927-12-31 23:54:08
look like they are exactly one second apart. But SimpleDateFormat does not parse them in a vacuum. It interprets them using a time zone. Since no time zone was set explicitly, it uses the JVM's default time zone, which in this case is:
Asia/Shanghai
That matters because time zones are not just fixed offsets like +08:00. They also contain historical rules. In some places and years, the local clock changed by odd amounts, not just by one hour. Around the date in your example, Shanghai had a historical offset transition. Because of that transition, one local clock time and the next visible second do not necessarily map to instants one second apart.
Key idea
- A
Stringlike"1927-12-31 23:54:07"is a local date-time. - A
Datein Java is an instant: milliseconds since the Unix epoch in UTC.
Mental Model
Think of a date-time string like a street address without a city.
23:54:07
by itself is not a unique global location in time. To know where it lands on the universal timeline, Java also needs the time zone, like adding the city to the address.
Now imagine that in that city, the street numbering changed historically. One house number might be skipped, or two house numbers might point to unusual positions. That is what time zone transitions do to local clock times.
So even though these two strings look next door to each other on the wall clock, once Java places them on the global UTC timeline, they may end up 353 seconds apart.
A useful mental model:
String= what a clock on the wall saysTimeZone= the rules of that location, including historyDate= the exact point on the world timeline
If the wall clock rules changed at that moment in history, simple-looking times can map strangely.
Syntax and Examples
Basic parsing with SimpleDateFormat
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sf.parse("1927-12-31 23:54:08");
This parses text into a Date, but it uses the default JVM time zone unless you set one.
Setting a time zone explicitly
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date d1 = sf.parse("1927-12-31 23:54:07");
Date d2 = sf.parse("1927-12-31 23:54:08");
System.out.println((d2.getTime() - d1.getTime()) / 1000);
In UTC, these two strings are interpreted using a stable offset with no Shanghai historical transition involved, so the difference will be exactly what the text suggests: 1 second.
Step by Step Execution
Consider this code:
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d1 = sf.parse("1927-12-31 23:54:07");
Date d2 = sf.parse("1927-12-31 23:54:08");
long diff = (d2.getTime() - d1.getTime()) / 1000;
System.out.println(diff);
What happens step by step
1. Create the formatter
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
This formatter knows the pattern, but because no time zone is set, it will use the JVM default time zone.
In your environment, that is:
Asia/Shanghai
2. Parse the first string
Date d1 sf.parse();
Real World Use Cases
1. Importing old records
If you import historical timestamps from CSV files, archives, or government systems, the local times may cross old time zone transitions. A value that looks valid in the source file may not behave as expected after parsing.
2. Financial and legal systems
Historical date accuracy matters in:
- banking records
- contracts
- audit logs
- policy start/end dates
Using the wrong time zone rules can shift timestamps and cause incorrect ordering.
3. Log analysis
If server logs are stored as local time instead of UTC, comparing timestamps around a zone transition can produce strange durations.
4. Data migration
When migrating from older Java versions or older operating systems, the bundled time zone database may differ. The same local timestamp can map differently if historical rules changed in the tz database.
5. Scheduling systems
Calendar apps and reminder systems must distinguish between:
- a local time entered by a user
- the actual instant when an event occurs
This is especially important for recurring events and historical event reconstruction.
Real Codebase Usage
In real projects, developers usually avoid this problem by being explicit about time handling.
Common patterns
Set the time zone explicitly
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
This avoids machine-dependent results.
Store instants in UTC
A common practice is:
- parse input with a known zone
- convert to UTC
- store the result as an instant or epoch milliseconds
Keep local date-time separate from instant
In modern Java:
LocalDateTime= no zone attachedZonedDateTime= local time plus zone rulesInstant= absolute timeline point
That separation prevents many bugs.
Validate assumptions at boundaries
For data near midnight, DST changes, or historical transitions, developers often add tests such as:
assertNotNull(parsedDate);
assertEquals(expectedSeconds, actualSeconds);
Use guard clauses for parsing configuration
Common Mistakes
1. Assuming a date string is an absolute instant
A string like this:
"1927-12-31 23:54:08"
is not a unique point in time until you apply a time zone.
How to avoid it
Always ask:
- What zone is this string meant to be in?
- Is it local wall-clock time or UTC?
2. Relying on the default JVM time zone
Broken approach:
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sf.parse("1927-12-31 23:54:08");
This behaves differently on machines with different default zones.
Better
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
3. Thinking all offset changes are whole hours
Comparisons
| Concept | What it represents | Time zone involved? | Good for |
|---|---|---|---|
String like "1927-12-31 23:54:08" | Local text representation | Not by itself | Input/output |
Date | Absolute instant | Already resolved during parsing | Legacy Java timestamp storage |
LocalDateTime | Date and time without zone | No | User-entered local times |
ZonedDateTime | Local date-time plus zone rules | Yes | Correct real-world time handling |
Instant |
Cheat Sheet
Quick rules
Datestores an instant, not a local date-time string.SimpleDateFormat.parse()uses the default JVM time zone unless you callsetTimeZone().- Historical time zone rules can change offsets by unusual amounts.
- Two local times that look 1 second apart may map to instants far apart.
- Do not rely on the machine default time zone for parsing.
Legacy Java syntax
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date d = sf.parse("1927-12-31 23:54:08");
Modern Java syntax
LocalDateTime local = LocalDateTime.parse("1927-12-31T23:54:08");
ZonedDateTime zoned = local.atZone(ZoneId.of("Asia/Shanghai"));
Instant instant = zoned.toInstant();
Safe habits
FAQ
Why does Java think two times 1 second apart are 353 seconds apart?
Because the strings are parsed in a historical time zone (Asia/Shanghai) where the offset changed around that moment. After conversion to absolute instants, the difference becomes 353 seconds.
Is this a bug in Date.getTime()?
No. getTime() returns the epoch milliseconds of the parsed instant correctly. The surprising behavior comes from how the local strings were converted to instants.
Why does it only happen for some timestamps?
Because only timestamps near a time zone transition are affected. Outside that boundary, neighboring local times usually map to neighboring instants.
Does the default time zone really matter when parsing?
Yes. If you do not explicitly set a time zone on SimpleDateFormat, Java uses the JVM default zone.
Would parsing in UTC fix this?
Yes, if your input strings are meant to be interpreted in UTC. Then a 1-second wall-clock difference will map to a 1-second instant difference.
Why are historical dates especially tricky?
Because time zone histories contain real changes in local offset, and not all of them are simple one-hour daylight saving changes.
Should I still use SimpleDateFormat in new Java code?
For new code, prefer java.time classes like LocalDateTime, ZonedDateTime, and . They model time concepts more clearly.
Mini Project
Description
Build a small Java program that parses the same local date-time strings in different time zones and compares the resulting epoch-second differences. This demonstrates that the same text can map to different instants depending on the zone rules used.
Goal
Parse two date-time strings, compute the difference in seconds, and show how the result changes between Asia/Shanghai and UTC.
Requirements
- Parse two date-time strings using the pattern
yyyy-MM-dd HH:mm:ss. - Compute the epoch-second difference between the parsed values.
- Run the comparison once using
Asia/Shanghaiand once usingUTC. - Print the parsed
Datevalues and the difference in seconds. - Keep the program self-contained and runnable from
main.
Keep learning
Related questions
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Choosing a @NotNull Annotation in Java: Validation vs Static Analysis
Learn how Java @NotNull annotations differ, when to use each one, and how to choose between validation, IDE hints, and static analysis tools.
Convert a Java Stack Trace to a String
Learn how to convert a Java exception stack trace to a string using StringWriter and PrintWriter, with examples and common mistakes.