/*
* 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.jkiss.utils.time;
import java.sql.Timestamp;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Formatter adapted to support nanoseconds from java.sql.Timestanp.
*/
public class ExtendedDateFormat extends SimpleDateFormat {
private static final String NINE_ZEROES = "000000000";
public static final int MAX_NANO_LENGTH = 8;
int nanoStart = -1, nanoLength;
boolean nanoOptional;
String nanoPrefix, nanoPostfix;
public ExtendedDateFormat(String pattern)
{
this(pattern, Locale.getDefault());
}
public ExtendedDateFormat(String pattern, Locale locale)
{
super(stripNanos(pattern), locale);
int quoteCount = 0;
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == '\'') {
quoteCount++;
for (int k = i + 1; k < pattern.length(); k++) {
if (pattern.charAt(k) == '\'') {
if (k != i + 1) {
quoteCount++;
}
i = k;
break;
}
}
} else if (c == '[') {
nanoStart = i;
nanoOptional = true;
for (int k = i + 1; k < pattern.length(); k++) {
if (pattern.charAt(k) == 'f') {
nanoLength++;
if (nanoPrefix == null) {
nanoPrefix = pattern.substring(i + 1, k);
}
}
if (pattern.charAt(k) == ']') {
nanoPostfix = pattern.substring(i + 1 + nanoPrefix.length() + nanoLength, k);
i = k + 1;
break;
}
}
} else if (c == 'f') {
nanoStart = i - quoteCount;
nanoOptional = false;
for (int k = i + 1; k < pattern.length(); k++) {
if (pattern.charAt(k) != 'f') {
break;
}
nanoLength++;
}
nanoLength++;
i = i + nanoLength;
}
}
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos)
{
StringBuffer result = super.format(date, toAppendTo, pos);
if (nanoStart >= 0) {
long nanos = 0;
if (date instanceof Timestamp) {
nanos = ((Timestamp) date).getNanos();
}
if (!nanoOptional || nanos > 0) {
StringBuilder nanosRes = new StringBuilder(nanoLength);
// Append nanos value in the end
if (nanoPrefix != null) {
nanosRes.append(nanoPrefix);
}
String nanoStr = String.valueOf(nanos);
// nanoStr must be a string of exactly 9 chars in length. Pad with leading "0" if not
int nbZeroesToPad = 9 - nanoStr.length();
if (nbZeroesToPad > 0) {
nanoStr = NINE_ZEROES.substring(0, nbZeroesToPad) + nanoStr;
}
if (nanoLength < nanoStr.length()) {
// Truncate nanos string to fit in the pattern
nanoStr = nanoStr.substring(0, nanoLength);
} else {
// Pad with 0s
for (int i = 0; i < nanoLength - nanoStr.length(); i++) {
nanosRes.append("0");
}
}
nanosRes.append(nanoStr);
if (nanoPostfix != null) {
nanosRes.append(nanoPostfix);
}
result.insert(nanoStart, nanosRes.toString());
}
}
return result;
}
@Override
public Date parse(String text, ParsePosition pos)
{
Date date = super.parse(text, pos);
int index = pos.getIndex();
if (index < text.length() && nanoStart > 0) {
long nanos = 0;
for (int i = 0; i < nanoLength; i++) {
int digitPos = index + i;
if (digitPos == text.length()) {
break;
}
char c = text.charAt(digitPos);
if (!Character.isDigit(c)) {
pos.setErrorIndex(index);
pos.setIndex(index);
//throw new ParseException("Invalid nanosecond character at pos " + digitPos + ": " + c, index);
return null;
}
long digit = ((int)c - (int)'0');
for (int k = MAX_NANO_LENGTH - i; k > 0; k--) {
digit *= 10;
}
nanos += digit;
}
if (nanos > 0) {
Timestamp ts = new Timestamp(date.getTime());
ts.setNanos((int)nanos);
return ts;
}
}
return date;
}
private static String stripNanos(String pattern)
{
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == '\'') {
for (int k = i + 1; k < pattern.length(); k++) {
if (pattern.charAt(k) == '\'') {
i = k;
break;
}
}
} else if (c == '[') {
for (int k = i + 1; k < pattern.length(); k++) {
if (pattern.charAt(k) == ']') {
return pattern.substring(0, i) + pattern.substring(k + 1);
}
}
} else if (c == 'f') {
for (int k = i + 1; k < pattern.length(); k++) {
if (pattern.charAt(k) != 'f') {
return pattern.substring(0, i) + pattern.substring(k);
}
}
return pattern.substring(0, i);
}
}
return pattern;
}
public static void main(String[] args)
{
test("'TIMESTAMP '''yyyy-MM-dd HH:mm:ss.ffffff''");
test("yyyy-MM-dd Z hh:mm:ss[.fffffffff]");
test("yyyy-MM-dd Z hh:mm:ss.fffffffff");
test("yyyy-MM-dd Z hh:mm:ss");
test("yyyy-MM-dd Z hh:mm:ss[.fffffffff nanos]");
test("yyyy-MM-dd Z hh:mm:ss[.ffffff micros]");
test("yyyy-MM-dd Z hh:mm:ss.ffffff");
test("yyyy-MM-dd Z hh:mm:ss.f"); // 1/10 secs = 'S'
}
private static void test(String pattern)
{
ExtendedDateFormat edf = new ExtendedDateFormat(pattern);
Timestamp date = new Timestamp(System.currentTimeMillis());
System.out.println(edf.format(date));
date.setNanos(0);
System.out.println(edf.format(date));
}
}