JXDatePicker использует SimpleDateFormat для форматирования dd.MM.yy в dd.MM.yyyy с текущим веком
Как уже объяснялось, я хочу добиться, чтобы, когда пользователь редактировал дату в JXDatePicker, он мог выбирать, набирает ли он ее снова в том же формате, который по умолчанию равен dd.MM.yyyy или просто dd.MM.yy, Когда он использует короткую форму, я хочу, чтобы сборщик выбрал текущий век.
Пример:
27.01.2012 edited to 27.01.10 should result in 27.01.2010
так же как:
27.01.2012 edited to 27.01.2010 should also result in 27.01.2010
По умолчанию JXDatePicker обрабатывает его следующим образом:
27.01.2012 edited to 27.01.10 results in 27.01.0010
Что не совсем так, как я хотел, чтобы это работало. После небольшого исследования я нашел следующий метод в SimpleDateFormat
/**
* Sets the 100-year period 2-digit years will be interpreted as being in
* to begin on the date the user specifies.
*
* @param startDate During parsing, two digit years will be placed in the range
* <code>startDate</code> to <code>startDate + 100 years</code>.
*/
public void set2DigitYearStart(Date startDate)
На первый взгляд это звучало так, как мне нужно. Поэтому я проверил это, и, к сожалению, это не сработало так, как я надеялся. Это потому, что я хочу использовать dd.MM.yyyy в качестве формата для отображения дат, а также хочу, чтобы он отображался в режиме редактирования. Например, когда пользователь кликает на дату, например 27.01.2012, я также хочу, чтобы она была такой же в режиме редактирования, а не только в краткой форме: 27.01.12.
Моя проблема сейчас заключается в том, что set2DigitYearStart(Date), к сожалению, работает только тогда, когда я выбираю использование сокращения в режиме редактирования. Я сделал небольшой пример, чтобы показать этот случай (библиотека SwingX требуется, потому что jxdatepicker и может быть найден здесь).
public class DatePickerExample extends JPanel
{
static JFrame frame;
public DatePickerExample()
{
JXDatePicker picker = new JXDatePicker();
JTextField field = new JTextField( 10 );
add( field );
add( picker );
final Calendar instance = Calendar.getInstance();
instance.set( 2012, 01, 26 );
Date date = instance.getTime();
picker.setDate( date );
// SimpleDateFormat format = new SimpleDateFormat( "dd.MM.yy" );//Works, but I wonna display and edit it with dd.MM.yyyy
SimpleDateFormat format = new SimpleDateFormat( "dd.MM.yyyy" );
final Date startDate = new Date( 0 );//01.01.1970
format.set2DigitYearStart( startDate );
picker.setFormats( format );
}
public static void main( String[] args )
{
frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setBounds( 400, 400, 400, 400 );
frame.setLayout( new BorderLayout() );
frame.add( new DatePickerExample() );
frame.setVisible( true );
}
}
Кто-нибудь уже имел такое же требование и может сказать мне, как сделать эту работу? Любые идеи приветствуются. Заранее большое спасибо. ymene
3 ответа
Финал (надеюсь:)
Краткое содержание первого редактирования:
- DatePickerFormatter уже реализует стратегию поиска (или CompoundFormat, как предложено @Robin)
- последовательность поиска для разбора настраивается клиентским кодом
- Идея состоит в том, чтобы попытаться выполнить синтаксический анализ, начиная с первого (обычно "самого длинного"), если это не удается, попробуйте следующий (обычно "не очень длинный") и т. д. до тех пор, пока не будет выполнено или не возникнет исключение parseException
- для годичного анализа у SimpleDateFormat есть правила, которые конфликтуют с этим самым длинным первым поиском: он требует, чтобы "yy" пробовался перед "yyyy"
- это делает в datePicker нежелательный побочный эффект - всегда показывать дату в формате короткого года
Причина в DatePickerFormatter: он не позволяет указывать формат форматирования (просто использует первый). Выходом является пользовательский DatePickerFormatter, который его поддерживает (во фрагменте он жестко запрограммирован на использование второго):
SimpleDateFormat longFormat = new SimpleDateFormat( "dd.MM.yyyy" );
SimpleDateFormat shortFormat = new SimpleDateFormat( "dd.MM.yy" );
Date startDate = new Date( 0 );//01.01.1970
shortFormat.set2DigitYearStart( startDate );
DatePickerFormatter formatter = new DatePickerFormatter(
// invers sequence for parsing to satisfy the year parsing rules
new DateFormat[] {shortFormat, longFormat}) {
@Override
public String valueToString(Object value) throws ParseException {
if (value == null) return null;
return getFormats()[1].format(value);
}
} ;
DefaultFormatterFactory factory = new DefaultFormatterFactory(formatter );
picker.getEditor().setFormatterFactory(factory);
Не совсем уверен, следует ли нам поддерживать настройку форматера в базовом классе. DatePickerFormatter - немного странный зверь, не расширяющий InternalFormatter, а процесс поиска немного конкурирует с FormatterFactory...
оригинал
Это не совсем datePicker, который обрабатывает это таким образом, это форматирование ядра (как уже отмечал D1e). Ни один из форматов по умолчанию /ter/s не поддерживает два формата одновременно: чтобы увидеть, попытайтесь достичь своей цели с помощью ядра JFormattedTextField:-)
Выходом может быть FormatterFactory: он позволяет использовать разные форматы в зависимости от контекста: отображать и редактировать - последний используется, когда поле сфокусировано, первый - во всех других случаях. Поскольку редактор выбора - это JFormattedTextField, вы можете настроить его напрямую (вместо использования методов setFormats)
SimpleDateFormat format = new SimpleDateFormat( "dd.MM.yyyy" );
SimpleDateFormat editFormat = new SimpleDateFormat( "dd.MM.yy" );
final Date startDate = new Date( 0 );//01.01.1970
instance.setTime(startDate);
editFormat.set2DigitYearStart( instance.getTime() );
DefaultFormatterFactory factory = new DefaultFormatterFactory(
new DatePickerFormatter(new DateFormat[] {format}),
new DatePickerFormatter(new DateFormat[] {format}),
new DatePickerFormatter(new DateFormat[] {editFormat})
);
picker.getEditor().setFormatterFactory(factory);
редактировать
после того, как он прочитал недавний ответ Робина (+1!), он стучит головой - наконец, после многих лет я смущаюсь, что SwingX' DatePickerFormatter пытается сделать: поддерживать цепочку форматирования (от длинного к короткому), дольше всего используется после фиксации, короче для облегчения набора текста пользователями.
К сожалению, это не работает так, как ожидалось. Учитывая последовательность форматов, длиннее и короче (и соответствующим образом настроен на столетие):
"yyyy", "yy"
и учитывая вход
"10"
такое ощущение, будто меня передают с первого на второе, что приводит к
2010
но нет. Как задокументировано (кто читает документацию... ленивый, кашляет...) в SimpleDateFormat
Год: [ ... ] Для синтаксического анализа, если количество букв шаблона превышает 2, год интерпретируется буквально, независимо от количества цифр. Таким образом, используя шаблон "MM/dd/yyyy", "01/11/12" анализирует до 11 января 12 года нашей эры.
В конце дня - поскольку DatePickerFormatter пытается поддержать этот поиск, но не удается - в конце концов, это может считаться проблемой SwingX:-)
Я не совсем осведомлен о JXDatePicker конкретно, но если конкретная функциональность, которую вы хотите смоделировать, такова: оба пользовательских ввода 27.01.2010 и 27.01.10 независимо должны привести к 27.01.2010
Тогда это будет работать:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) throws ParseException {
String inputLiteralDateYY = "27.01.10"; //Also works with "27.01.97"
String inputLiteralDateYYYY = "27.01.2010"; //Also works with "27.01.1997"
DateFormat dfYYYY = new SimpleDateFormat("dd.MM.yyyy");
DateFormat dfYY = new SimpleDateFormat("dd.MM.yy");
Date dateFromYY = dfYY.parse(inputLiteralDateYY);
Date dateFromYYYY = dfYY.parse(inputLiteralDateYYYY);
String outputLiteralDateFromYY = dfYYYY.format(dateFromYY);
String outputLiteralDateFromYYYY = dfYYYY.format(dateFromYYYY);
System.out.println(outputLiteralDateFromYY);
System.out.println(outputLiteralDateFromYYYY);
}
}
Дело в том, что сначала вы анализируете ввод с помощью шаблона "dd.MM.yy", а затем возвращаете его форматирование с использованием шаблона "dd.MM.yyyy".
Надеюсь, это поможет или поможет применить это к вашему сценарию.
Клеопатра уже объяснила, как установить Format
на дату выбора. Для этого варианта использования я бы применил комбинацию CompositeFormat
а также ParseAllFormat
вместо того, чтобы иметь отдельный формат для редактирования и обычный режим, чтобы избежать изменения String
когда вы начинаете редактирование (как вы уже заметили).
Составной формат
Составной формат, как следует из названия, является составной реализацией Format
класс но только для разбора. Для форматирования он использует один Format
, Это позволяет пользователю вводить свою дату во многих формах, в то время как она последовательно форматируется с использованием одного определенного формата для форматирования.
Вы также можете получить это поведение, написав еще один сложный Format
, Но в этом случае проще использовать функциональность форматирования / разбора, предлагаемую SimpleDateFormat
класс JDK.
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Composite form of {@link java.text.Format Format}. It uses multiple formats for parsing, and
* only one format for formatting.</p>
*
* <p>A possible use-case is the formatting of user input (e.g. in a {@code JFormattedTextField}).
* Multiple formats for parsing allows accepting multiple forms of user input without having to
* write a complicated format.</p>
*/
public class CompositeFormat extends Format {
private List<Format> fFormats = new ArrayList<>();
private Format fFormattingFormat;
/**
* Create a new
*/
public CompositeFormat() {
}
/**
* Add a format to this composite format
*
* @param aFormat The format to add
*/
public void addFormat( Format aFormat ) {
assertNotNull( aFormat, "You cannot add a null Format" );
if ( !( fFormats.contains( aFormat ) ) ) {
fFormats.add( aFormat );
}
}
/**
* Remove a format from this composite format
*
* @param aFormat The format to remove
*/
public void removeFormat( Format aFormat ) {
assertNotNull( aFormat, "You cannot remove a null Format" );
fFormats.remove( aFormat );
updateFormattingFormat();
}
/**
* Sets <code>aFormat</code> as the format which will be used for formatting the
* objects. The format will also be added to the list of available formats.
* @param aFormat The format which will be used for formatting
*/
public void setFormattingFormat( Format aFormat ){
assertNotNull( aFormat, "Formatting format may not be null" );
addFormat( aFormat );
fFormattingFormat = aFormat;
}
private void assertNotNull( Object aObjectToCheck, String aMessage ) {
if ( aObjectToCheck == null ) {
throw new NullPointerException( aMessage );
}
}
private void updateFormattingFormat(){
if ( !( fFormats.contains( fFormattingFormat ) ) ){
fFormattingFormat = null;
if ( !( fFormats.isEmpty() ) ){
fFormattingFormat = fFormats.iterator().next();
}
}
}
@Override
public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) {
assertNotNull( fFormattingFormat, "Set a formatting format before using this format" );
return fFormattingFormat.format( obj, toAppendTo, pos );
}
@Override
public Object parseObject( String source, ParsePosition pos ) {
if ( fFormats.isEmpty() ){
throw new UnsupportedOperationException( "Add at least one format before using this composite format" );
}
Format formatToUse = fFormats.iterator().next();
int maxIndex = pos.getIndex();
for ( Format format : fFormats ) {
ParsePosition tempPos = new ParsePosition( pos.getIndex() );
tempPos.setErrorIndex( pos.getErrorIndex() );
format.parseObject( source, tempPos );
if ( tempPos.getIndex() > maxIndex ){
maxIndex = tempPos.getIndex();
formatToUse = format;
if( maxIndex == source.length() ){
//found a format which parses the whole string
break;
}
}
}
return formatToUse.parseObject( source, pos );
}
}
ParseAllFormat
Обычно для пользовательского ввода вы хотите, чтобы весь пользовательский ввод мог быть отформатирован / проанализирован, чтобы пользователь не мог вводить строку, которая является полу-правильной. ParseAllFormat
это декоратор для обычного Format
который бросает ParseException
когда только часть String
можно разобрать.
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParseException;
import java.text.ParsePosition;
/**
* <p>Decorator for a {@link Format Format} which only accepts values which can be completely parsed
* by the delegate format. If the value can only be partially parsed, the decorator will refuse to
* parse the value.</p>
*/
public class ParseAllFormat extends Format {
private final Format fDelegate;
/**
* Decorate <code>aDelegate</code> to make sure if parser everything or nothing
*
* @param aDelegate The delegate format
*/
public ParseAllFormat( Format aDelegate ) {
fDelegate = aDelegate;
}
@Override
public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) {
return fDelegate.format( obj, toAppendTo, pos );
}
@Override
public AttributedCharacterIterator formatToCharacterIterator( Object obj ) {
return fDelegate.formatToCharacterIterator( obj );
}
@Override
public Object parseObject( String source, ParsePosition pos ) {
int initialIndex = pos.getIndex();
Object result = fDelegate.parseObject( source, pos );
if ( result != null && pos.getIndex() < source.length() ) {
int errorIndex = pos.getIndex();
pos.setIndex( initialIndex );
pos.setErrorIndex( errorIndex );
return null;
}
return result;
}
@Override
public Object parseObject( String source ) throws ParseException {
//no need to delegate the call, super will call the parseObject( source, pos ) method
return super.parseObject( source );
}
}
Сочетание этих двух классов позволяет следующий код
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class FormattingDemo {
private static Format createCompositeDateFormat(){
Format formattingFormat = new ParseAllFormat( new SimpleDateFormat( "dd.MM.yyyy" ) );
SimpleDateFormat shortFormat = new SimpleDateFormat( "dd.MM.yy" );
Format otherFormat = new ParseAllFormat( shortFormat );
CompositeFormat compositeFormat = new CompositeFormat();
compositeFormat.addFormat( otherFormat );
compositeFormat.addFormat( formattingFormat );
compositeFormat.setFormattingFormat( formattingFormat );
return compositeFormat;
}
public static void main( String[] args ) throws ParseException {
Format dateFormat = createCompositeDateFormat();
System.out.println( dateFormat.parseObject( "27.01.2010" ) );
System.out.println( dateFormat.parseObject( "27.01.10" ) );
System.out.println( dateFormat.parseObject( "27.01.2012" ) );
System.out.println(dateFormat.format( dateFormat.parseObject( "27.01.2010" ) ));
System.out.println(dateFormat.format( dateFormat.parseObject( "27.01.10" ) ));
System.out.println(dateFormat.format( dateFormat.parseObject( "27.01.2012" ) ));
}
}
приводя к следующему выводу
Wed Jan 27 00:00:00 CET 2010
Wed Jan 27 00:00:00 CET 2010
Fri Jan 27 00:00:00 CET 2012
27.01.2010
27.01.2010
27.01.2012
Обратите внимание, что есть небольшой улов, для которого я не нашел достойного решения. Порядок, в котором вы добавляете Format
экземпляры к CompositeFormat
также порядок, в котором они оцениваются для анализа. В этом случае вам нужно добавить их в правильном порядке, так как даже new SimpleDateFormat( "dd.MM.yyyy" )
кажется, принимает входную строку 27.01.10
и может разобрать весь String
к Date
объект эквивалентен 27.01.0010
,