You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2015/08/21 05:26:46 UTC

logging-log4j2 git commit: LOG4J2-1097 initial version of CustomTimeFormat

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 31b7e7be5 -> 87bde0d5b


LOG4J2-1097 initial version of CustomTimeFormat

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/87bde0d5
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/87bde0d5
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/87bde0d5

Branch: refs/heads/master
Commit: 87bde0d5b4d27b979d273d902a7713e5f0ac75fd
Parents: 31b7e7b
Author: rpopma <rp...@apache.org>
Authored: Fri Aug 21 12:26:56 2015 +0900
Committer: rpopma <rp...@apache.org>
Committed: Fri Aug 21 12:26:56 2015 +0900

----------------------------------------------------------------------
 .../core/util/datetime/CustomTimeFormat.java    | 277 +++++++++++++++++++
 1 file changed, 277 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/87bde0d5/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
new file mode 100644
index 0000000..1388a90
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.util.datetime;
+
+import java.util.Calendar;
+import java.util.Objects;
+
+/**
+ * Custom time formatter that trades flexibility for performance.
+ */
+public class CustomTimeFormat {
+    /**
+     * Enumeration over the supported date/time format patterns.
+     */
+    public static enum FixedFormat {
+        /**
+         * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
+         */
+        ABSOLUTE("HH:mm:ss,SSS", null, ':', 1, ',', 1),
+
+        /**
+         * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
+         */
+        ABSOLUTE2("HH:mm:ss.SSS", null, ':', 1, '0', 1),
+
+        /**
+         * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
+         */
+        COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", ' ', 0, ' ', 0),
+
+        /**
+         * DATE time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
+         */
+        DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", ':', 1, ',', 1),
+
+        /**
+         * DATE time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
+         */
+        DATE2("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", ':', 1, '.', 1),
+
+        /**
+         * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
+         */
+        DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", ':', 1, ',', 1),
+
+        /**
+         * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
+         */
+        DEFAULT2("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", ':', 1, '.', 1),
+
+        /**
+         * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
+         */
+        ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", ' ', 0, ',', 1),
+
+        /**
+         * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
+         */
+        ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", ':', 1, ',', 1), ;
+
+        private final String pattern;
+        private final String datePattern;
+        private final char timeSeparatorChar;
+        private final int timeSeparatorLength;
+        private final char millisSeparatorChar;
+        private final int millisSeparatorLength;
+
+        private FixedFormat(final String pattern, final String datePattern, char timeSeparator, int timeSepLength,
+                char millisSeparator, int millisSepLength) {
+            this.timeSeparatorChar = timeSeparator;
+            this.timeSeparatorLength = timeSepLength;
+            this.millisSeparatorChar = millisSeparator;
+            this.millisSeparatorLength = millisSepLength;
+            this.pattern = Objects.requireNonNull(pattern);
+            this.datePattern = datePattern; // may be null
+        }
+
+        public String getPattern() {
+            return pattern;
+        }
+
+        public String getDatePattern() {
+            return datePattern;
+        }
+
+        /**
+         * Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found.
+         */
+        public static FixedFormat lookup(String nameOrPattern) {
+            for (FixedFormat type : FixedFormat.values()) {
+                if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
+                    return type;
+                }
+            }
+            return null;
+        }
+
+        public int getDatePatternLength() {
+            return getDatePattern() == null ? 0 : getDatePattern().length();
+        }
+
+        public FastDateFormat getFastDateFormat() {
+            return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern());
+        }
+    }
+
+    public static CustomTimeFormat createIfSupported(final String[] options) {
+        if (options == null || options.length == 0 || options.length > 1) {
+            return null; // time zone not supported
+        }
+        FixedFormat type = FixedFormat.lookup(options[0]);
+        return type == null ? null : new CustomTimeFormat(type);
+    }
+
+    private final FixedFormat type;
+    private final int length;
+    private final int dateLength;
+    private final FastDateFormat fastDateFormat; // may be null
+    private final char timeSeparatorChar;
+    private final char millisSeparatorChar;
+    private final int timeSeparatorLength;
+    private final int millisSeparatorLength;
+
+    private volatile long midnightToday = 0;
+    private volatile long midnightTomorrow = 0;
+    private char[] cachedDate; // may be null
+
+    /**
+     * Constructs a CustomTimeFormat for the specified fixed format.
+     * <p>
+     * Package protected for unit tests.
+     * 
+     * @param type the fixed format
+     */
+    CustomTimeFormat(FixedFormat type) {
+        this.type = Objects.requireNonNull(type);
+        this.timeSeparatorChar = type.timeSeparatorChar;
+        this.timeSeparatorLength = type.timeSeparatorLength;
+        this.millisSeparatorChar = type.millisSeparatorChar;
+        this.millisSeparatorLength = type.millisSeparatorLength;
+        this.length = type.getPattern().length();
+        this.dateLength = type.getDatePatternLength();
+        this.fastDateFormat = type.getFastDateFormat();
+    }
+
+    public String getFormat() {
+        return type.getPattern();
+    }
+
+    // 21 bytes (allows immediate JVM inlining: < 35 bytes)
+    private long millisSinceMidnight(final long now) {
+        if (now >= midnightTomorrow) {
+            updateMidnightMillis(now);
+        }
+        return now - midnightToday;
+    }
+
+    private void updateMidnightMillis(final long now) {
+
+        // cachedDate does not need to be volatile because
+        // there is a write to a volatile field *after* cachedDate is modified,
+        // and there is a read from a volatile field *before* cachedDate is read.
+        // The Java memory model guarantees that because of the above,
+        // changes to cachedDate in one thread are visible to other threads.
+        updateCachedDate(now);
+
+        midnightToday = calcMidnightMillis(0);
+        midnightTomorrow = calcMidnightMillis(1);
+    }
+
+    private long calcMidnightMillis(final int addDays) {
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        cal.add(Calendar.DATE, addDays);
+        return cal.getTimeInMillis();
+    }
+
+    private void updateCachedDate(long now) {
+        if (fastDateFormat != null) {
+            StringBuilder result = fastDateFormat.format(now, new StringBuilder());
+            cachedDate = result.toString().toCharArray();
+        }
+    }
+
+    // 28 bytes (allows immediate JVM inlining: < 35 bytes)
+    public String format(final long time) {
+        final char[] result = new char[length];
+        int written = format(time, result, 0);
+        return new String(result, 0, written);
+    }
+
+    // 31 bytes (allows immediate JVM inlining: < 35 bytes)
+    public int format(final long time, final char[] buffer, int startPos) {
+        // Calculate values by getting the ms values first and do then
+        // calculate the hour minute and second values divisions.
+
+        // Get daytime in ms: this does fit into an int
+        // int ms = (int) (time % 86400000);
+        final int ms = (int) (millisSinceMidnight(time));
+        writeDate(buffer);
+        return writeTime(ms, buffer, startPos + dateLength) - startPos;
+    }
+
+    // 22 bytes (allows immediate JVM inlining: < 35 bytes)
+    private void writeDate(char[] buffer) {
+        if (cachedDate != null) {
+            System.arraycopy(cachedDate, 0, buffer, 0, dateLength);
+        }
+    }
+
+    // 262 bytes (will be inlined when hot enough: < 325 bytes)
+    private int writeTime(int ms, char[] buffer, int pos) {
+        final int hours = ms / 3600000;
+        ms -= 3600000 * hours;
+
+        final int minutes = ms / 60000;
+        ms -= 60000 * minutes;
+
+        final int seconds = ms / 1000;
+        ms -= 1000 * seconds;
+
+        // Hour
+        int temp = hours / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        // Do subtract to get remainder instead of doing % 10
+        buffer[pos++] = ((char) (hours - 10 * temp + '0'));
+        buffer[pos] = timeSeparatorChar;
+        pos += timeSeparatorLength;
+
+        // Minute
+        temp = minutes / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        // Do subtract to get remainder instead of doing % 10
+        buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
+        buffer[pos] = timeSeparatorChar;
+        pos += timeSeparatorLength;
+
+        // Second
+        temp = seconds / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+        buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
+        buffer[pos] = millisSeparatorChar;
+        pos += millisSeparatorLength;
+
+        // Millisecond
+        temp = ms / 100;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        ms -= 100 * temp;
+        temp = ms / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        ms -= 10 * temp;
+        buffer[pos++] = ((char) (ms + '0'));
+        return pos;
+    }
+}


Fwd: logging-log4j2 git commit: LOG4J2-1097 initial version of CustomTimeFormat

Posted by Gary Gregory <ga...@gmail.com>.
I can't say I'm in love with the "2" postfix on the names.

How about ABSOLUTE2 -> ABSOLUTE_PERIOD, and so on?

WRT:

// 28 bytes (allows immediate JVM inlining: < 35 bytes)

This is much better, thank you. I would add what version and vendor this is
based on, maybe like:

// 28 bytes (allows immediate JVM inlining: < 35 bytes)
// As of Oracle Java 1.7.0_51 on Windows

Gary




---------- Forwarded message ----------
From: <rp...@apache.org>
Date: Thu, Aug 20, 2015 at 8:26 PM
Subject: logging-log4j2 git commit: LOG4J2-1097 initial version of
CustomTimeFormat
To: commits@logging.apache.org


Repository: logging-log4j2
Updated Branches:
  refs/heads/master 31b7e7be5 -> 87bde0d5b


LOG4J2-1097 initial version of CustomTimeFormat

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit:
http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/87bde0d5
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/87bde0d5
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/87bde0d5

Branch: refs/heads/master
Commit: 87bde0d5b4d27b979d273d902a7713e5f0ac75fd
Parents: 31b7e7b
Author: rpopma <rp...@apache.org>
Authored: Fri Aug 21 12:26:56 2015 +0900
Committer: rpopma <rp...@apache.org>
Committed: Fri Aug 21 12:26:56 2015 +0900

----------------------------------------------------------------------
 .../core/util/datetime/CustomTimeFormat.java    | 277 +++++++++++++++++++
 1 file changed, 277 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/87bde0d5/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
new file mode 100644
index 0000000..1388a90
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.util.datetime;
+
+import java.util.Calendar;
+import java.util.Objects;
+
+/**
+ * Custom time formatter that trades flexibility for performance.
+ */
+public class CustomTimeFormat {
+    /**
+     * Enumeration over the supported date/time format patterns.
+     */
+    public static enum FixedFormat {
+        /**
+         * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
+         */
+        ABSOLUTE("HH:mm:ss,SSS", null, ':', 1, ',', 1),
+
+        /**
+         * ABSOLUTE time format variation with period separator: {@code
"HH:mm:ss.SSS"}.
+         */
+        ABSOLUTE2("HH:mm:ss.SSS", null, ':', 1, '0', 1),
+
+        /**
+         * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
+         */
+        COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", ' ', 0, ' ', 0),
+
+        /**
+         * DATE time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
+         */
+        DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", ':', 1, ',', 1),
+
+        /**
+         * DATE time format variation with period separator: {@code "dd
MMM yyyy HH:mm:ss.SSS"}.
+         */
+        DATE2("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", ':', 1, '.', 1),
+
+        /**
+         * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
+         */
+        DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", ':', 1, ',', 1),
+
+        /**
+         * DEFAULT time format variation with period separator: {@code
"yyyy-MM-dd HH:mm:ss.SSS"}.
+         */
+        DEFAULT2("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", ':', 1, '.', 1),
+
+        /**
+         * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
+         */
+        ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", ' ', 0, ',',
1),
+
+        /**
+         * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
+         */
+        ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", ':', 1, ',',
1), ;
+
+        private final String pattern;
+        private final String datePattern;
+        private final char timeSeparatorChar;
+        private final int timeSeparatorLength;
+        private final char millisSeparatorChar;
+        private final int millisSeparatorLength;
+
+        private FixedFormat(final String pattern, final String
datePattern, char timeSeparator, int timeSepLength,
+                char millisSeparator, int millisSepLength) {
+            this.timeSeparatorChar = timeSeparator;
+            this.timeSeparatorLength = timeSepLength;
+            this.millisSeparatorChar = millisSeparator;
+            this.millisSeparatorLength = millisSepLength;
+            this.pattern = Objects.requireNonNull(pattern);
+            this.datePattern = datePattern; // may be null
+        }
+
+        public String getPattern() {
+            return pattern;
+        }
+
+        public String getDatePattern() {
+            return datePattern;
+        }
+
+        /**
+         * Returns the FixedFormat with the name or pattern matching the
specified string or {@code null} if not found.
+         */
+        public static FixedFormat lookup(String nameOrPattern) {
+            for (FixedFormat type : FixedFormat.values()) {
+                if (type.name().equals(nameOrPattern) ||
type.getPattern().equals(nameOrPattern)) {
+                    return type;
+                }
+            }
+            return null;
+        }
+
+        public int getDatePatternLength() {
+            return getDatePattern() == null ? 0 :
getDatePattern().length();
+        }
+
+        public FastDateFormat getFastDateFormat() {
+            return getDatePattern() == null ? null :
FastDateFormat.getInstance(getDatePattern());
+        }
+    }
+
+    public static CustomTimeFormat createIfSupported(final String[]
options) {
+        if (options == null || options.length == 0 || options.length > 1) {
+            return null; // time zone not supported
+        }
+        FixedFormat type = FixedFormat.lookup(options[0]);
+        return type == null ? null : new CustomTimeFormat(type);
+    }
+
+    private final FixedFormat type;
+    private final int length;
+    private final int dateLength;
+    private final FastDateFormat fastDateFormat; // may be null
+    private final char timeSeparatorChar;
+    private final char millisSeparatorChar;
+    private final int timeSeparatorLength;
+    private final int millisSeparatorLength;
+
+    private volatile long midnightToday = 0;
+    private volatile long midnightTomorrow = 0;
+    private char[] cachedDate; // may be null
+
+    /**
+     * Constructs a CustomTimeFormat for the specified fixed format.
+     * <p>
+     * Package protected for unit tests.
+     *
+     * @param type the fixed format
+     */
+    CustomTimeFormat(FixedFormat type) {
+        this.type = Objects.requireNonNull(type);
+        this.timeSeparatorChar = type.timeSeparatorChar;
+        this.timeSeparatorLength = type.timeSeparatorLength;
+        this.millisSeparatorChar = type.millisSeparatorChar;
+        this.millisSeparatorLength = type.millisSeparatorLength;
+        this.length = type.getPattern().length();
+        this.dateLength = type.getDatePatternLength();
+        this.fastDateFormat = type.getFastDateFormat();
+    }
+
+    public String getFormat() {
+        return type.getPattern();
+    }
+
+    // 21 bytes (allows immediate JVM inlining: < 35 bytes)
+    private long millisSinceMidnight(final long now) {
+        if (now >= midnightTomorrow) {
+            updateMidnightMillis(now);
+        }
+        return now - midnightToday;
+    }
+
+    private void updateMidnightMillis(final long now) {
+
+        // cachedDate does not need to be volatile because
+        // there is a write to a volatile field *after* cachedDate is
modified,
+        // and there is a read from a volatile field *before* cachedDate
is read.
+        // The Java memory model guarantees that because of the above,
+        // changes to cachedDate in one thread are visible to other
threads.
+        updateCachedDate(now);
+
+        midnightToday = calcMidnightMillis(0);
+        midnightTomorrow = calcMidnightMillis(1);
+    }
+
+    private long calcMidnightMillis(final int addDays) {
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        cal.add(Calendar.DATE, addDays);
+        return cal.getTimeInMillis();
+    }
+
+    private void updateCachedDate(long now) {
+        if (fastDateFormat != null) {
+            StringBuilder result = fastDateFormat.format(now, new
StringBuilder());
+            cachedDate = result.toString().toCharArray();
+        }
+    }
+
+    // 28 bytes (allows immediate JVM inlining: < 35 bytes)
+    public String format(final long time) {
+        final char[] result = new char[length];
+        int written = format(time, result, 0);
+        return new String(result, 0, written);
+    }
+
+    // 31 bytes (allows immediate JVM inlining: < 35 bytes)
+    public int format(final long time, final char[] buffer, int startPos) {
+        // Calculate values by getting the ms values first and do then
+        // calculate the hour minute and second values divisions.
+
+        // Get daytime in ms: this does fit into an int
+        // int ms = (int) (time % 86400000);
+        final int ms = (int) (millisSinceMidnight(time));
+        writeDate(buffer);
+        return writeTime(ms, buffer, startPos + dateLength) - startPos;
+    }
+
+    // 22 bytes (allows immediate JVM inlining: < 35 bytes)
+    private void writeDate(char[] buffer) {
+        if (cachedDate != null) {
+            System.arraycopy(cachedDate, 0, buffer, 0, dateLength);
+        }
+    }
+
+    // 262 bytes (will be inlined when hot enough: < 325 bytes)
+    private int writeTime(int ms, char[] buffer, int pos) {
+        final int hours = ms / 3600000;
+        ms -= 3600000 * hours;
+
+        final int minutes = ms / 60000;
+        ms -= 60000 * minutes;
+
+        final int seconds = ms / 1000;
+        ms -= 1000 * seconds;
+
+        // Hour
+        int temp = hours / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        // Do subtract to get remainder instead of doing % 10
+        buffer[pos++] = ((char) (hours - 10 * temp + '0'));
+        buffer[pos] = timeSeparatorChar;
+        pos += timeSeparatorLength;
+
+        // Minute
+        temp = minutes / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        // Do subtract to get remainder instead of doing % 10
+        buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
+        buffer[pos] = timeSeparatorChar;
+        pos += timeSeparatorLength;
+
+        // Second
+        temp = seconds / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+        buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
+        buffer[pos] = millisSeparatorChar;
+        pos += millisSeparatorLength;
+
+        // Millisecond
+        temp = ms / 100;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        ms -= 100 * temp;
+        temp = ms / 10;
+        buffer[pos++] = ((char) (temp + '0'));
+
+        ms -= 10 * temp;
+        buffer[pos++] = ((char) (ms + '0'));
+        return pos;
+    }
+}




-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory