Skip to content

Fix blame timestamps by parsing svn:date at microsecond resolution - #96

Closed
marcmuel wants to merge 1 commit into
subclipse:masterfrom
marcmuel:fix/blame-date-microseconds
Closed

marcmuel wants to merge 1 commit into
subclipse:masterfrom
marcmuel:fix/blame-date-microseconds

Conversation

@marcmuel

@marcmuel marcmuel commented Sep 9, 2026

Copy link
Copy Markdown

Symptom

Every timestamp reported by blame/annotate through the JavaHL adapter is in
the future — by a different amount for each revision, up to ~16 minutes.

Root cause

javahl/src/main/java/org/tigris/subversion/svnclientadapter/javahl/JhlAnnotations.java:39
declares

private static final SafeSimpleDateFormat df = new SafeSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

and singleLine(...) parses the complete svn:date revprop with it (lines 88 and 92):

df.parse(new String(revProps.get("svn:date")))
df.parse(new String(mergedRevProps.get("svn:date")))

svn:date has microsecond resolution, e.g. 2011-07-31T12:58:47.840123Z.
SimpleDateFormat is lenient by default, so the SSS field swallows all six
fractional digits and interprets 840123 as milliseconds; the trailing Z
is ignored by parse(String) as trailing text. The result is shifted forward by
fraction milliseconds — 0 to 999999 ms, i.e. up to 16 min 39 s.

This is not a time zone problem: SafeSimpleDateFormat already pins its
internal formatter to UTC.

JhlLogMessage in the same package gets this right by truncating the string to
23 characters before parsing; JhlAnnotations never got the same treatment.

Reproduction

OpenJDK, TZ=Europe/Berlin, formatted as dd.MM.yyyy HH:mm:ss:

input before after
2026-07-31T12:58:47.840123Z 31.07.2026 15:12:47 31.07.2026 14:58:47
2026-07-31T12:58:47.999999Z 31.07.2026 15:15:26 31.07.2026 14:58:47
2026-01-15T09:00:00.000000Z 15.01.2026 10:00:00 15.01.2026 10:00:00

The drift is 999.0 s in the worst case and 0 s only when the fraction is zero.

Fix

Replaced the formatter with a private helper that parses via
java.time.Instant.parse(...):

private static Date parseSvnDate(byte[] date) {
    return Date.from(Instant.parse(new String(date)));
}

Why Instant rather than truncating to 23 characters like JhlLogMessage:
Instant.parse is the ISO-8601 parser this value actually calls for. It handles
any number of fractional digits and the Z suffix by specification instead of
relying on a fixed string length, it is strict (a malformed value fails instead
of being silently reinterpreted), and it removes the lenient-SimpleDateFormat
footgun from the file entirely. java.time is available — the build targets
Java 8 (maven-compiler-plugin source/target 1.8).

Because Instant.parse throws the unchecked DateTimeParseException rather
than ParseException, the existing catch was retargeted so a malformed value
still surfaces as a ClientException, as before.

Scope

  • Minimal diff, existing indentation untouched, no public signature changes.
  • The cmdline adapter is unaffected: Helper.xmlFormat uses
    yyyy-MM-dd'T'HH:mm:ss with no fractional-seconds field.
  • The svnkit adapter inherits the fix automatically, since
    SvnKitClientAdapter extends AbstractJhlClientAdapter and instantiates
    JhlAnnotations.
  • JhlLogMessage is left alone — its 23-character truncation is already correct.

Build note

mvn -pl base,javahl -am compile builds base but cannot resolve
org.apache.subversion:javahl:1.14.0, which is served from
https://maven.pkg.github.com/subclipse/javahl and returns 401 Unauthorized
without credentials. The changed file was therefore verified by compiling it
with javac --release 8 against the built base classes plus minimal stubs for
ClientException and BlameCallback; it compiles clean, so the change is
syntactically valid and Java 8 compatible. Please let CI do the real javahl build.

🤖 Generated with Claude Code

JhlAnnotations parsed the full svn:date revprop with a SimpleDateFormat
pattern ending in .SSS. svn:date has microsecond resolution, and the
lenient parser consumed all six fractional digits as milliseconds, so
every Date returned by blame/annotate was up to 999 seconds (~16 min)
in the future, by a different amount per revision.

Parse with java.time.Instant instead, which understands ISO-8601
fractional seconds and the trailing Z.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@markphip

markphip commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

This seems legit but I am just not sure if this code is really worth considering active or maintained any more. I certainly do not see any releases being produced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Date decoding remains platform-dependent, and the timestamp fix lacks regression coverage.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Fixes incorrect JavaHL blame timestamps by parsing svn:date with Instant.

Changes:

  • Replaces lenient millisecond parsing with ISO-8601 parsing.
  • Wraps parse failures as ClientException.
File summaries
File Description
JhlAnnotations.java Corrects blame timestamp parsing.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

* the trailing <code>Z</code> correctly.
*/
private static Date parseSvnDate(byte[] date) {
return Date.from(Instant.parse(new String(date)));
Comment on lines +50 to +51
private static Date parseSvnDate(byte[] date) {
return Date.from(Instant.parse(new String(date)));
@marcmuel

Copy link
Copy Markdown
Author

Thanks for pointing that out. Of course, that doesn't make sense.

@marcmuel marcmuel closed this Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants