Есть ли в JodaTime или Java 8 специальная поддержка даты и времени JD Edwards?
Тема под рукой - грязная, специфичная для домена проблема работы с датами в программном обеспечении ERP Oracle под названием JD Edwards. Его детали задокументированы в этом вопросе.
Прежде чем писать классы-обертки для обработки дат и времени из JD Edwards, я хочу узнать, представила ли JodaTime или Java 8 какую-либо специальную поддержку для этого уникального формата времени, или мне придется выполнять значительные манипуляции со строками независимо от используемых мной библиотек,
Это неясная проблема, поэтому, пожалуйста, отвечайте, только если у вас есть конкретные знания об этой проблеме и / или JodaTime/Java 8/JSR 310.
ДОПОЛНЕНИЕ: По запросу Бэзила Бурка, добавляя пример отметок времени, сопровождающих указанные даты. Вот два примера полей даты / времени из разных таблиц:
JCSBMDATE: 115100, JCSBMTIME: 120102.0
RLUPMJ: 114317, RLUPMT: 141805.0
Кроме того, переменная даты приводится как BigDecimal, а время - как Double. Итак, я, вероятно, буду держать строковые парсеры рядом, но также напишу фабричные методы, которые также принимают значения BigDecimal/Double.
Кажется, что поле времени на самом деле представляет собой количество миллисекунд (не секунд) от начала дня, и ".0" можно игнорировать. Итак, нужно будет выполнить преобразование и расчёт следующим образом:
localDate.atTime (LocalTime.ofNanoOfDay (Long.parseLong (jdeTime) * 1000000))
2 ответа
Дата Эдвардса определена
На самом деле, детали даты JD Edwards не так уж и хороши, согласно этому простому описанию на странице на Oracle.com:
О юлианском формате даты
Поля даты в файлах JD Edwards World хранятся в юлианском формате....
Юлианский (*JUL) формат даты - CYYDDD, где:
C добавляется к 19, чтобы создать век, то есть 0 + 19 = 19, 1 + 19 = 20. YY - год в столетии, DDD - день в году.
Термины:
- Я бы назвал
C
часть "смещения века", сколько веков добавить к19
, использование0
за19xx
годы и1
за20xx
года. - Платформа java.time вызывает
DDD
a DayOfYear, а "порядковая дата" - это другой термин. Использование "Джулиана" для числа дня в году является обычным, но не правильным, конфликтующим с Юлианским днем.
Инфраструктура java.time не включает в себя прямую поддержку синтаксического анализа или генерации строк этого формата, что я не могу найти.
JulianFields
Здесь java.time.temporal.JulianFields
но это для переопределенной версии юлианских дат, где мы считаем количество дней от эпохи (1970-01-01 (ISO), а не от исторического 24 ноября 4714 г. до н.э. (проглептический григорианский)), игнорируя при этом годы. Так что это не имеет ничего общего с определением Дж.Д. Эдвардса, вопреки некоторым неправильным советам на этой странице, на которые есть ссылки в Вопросе.
Порядковая дата
Эта дата JD Edwards является версией порядковой даты. Порядковая дата иногда упоминается случайно (и неправильно) как "юлианская" дата только потому, что она разделяет идею подсчета последовательности дней. Но порядковая дата отсчитывает дни с начала года до конца года для числа всегда между 1 и 365/366 (високосный год), не считая с некоторой эпохи и превращаясь в число в тысячи.
Вернемся к вопросу об обработке даты JD Edwards в java.time...
Нет, я не нахожу прямой или косвенной поддержки даты JD Edwards, встроенной в java.time.
Пакет java.date.format, кажется, не знает о столетии даты, только год и эпоху. Так что я не могу найти способ определить C
часть даты Дж. Д. Эдвардса.
Последняя часть даты JD Edwards, порядковое число дней в году, хорошо обрабатывается как в классах даты и времени, так и в классах форматирования.
Заворачивать LocalDate
Поскольку дата JD Edwards, очевидно, имеет ту же логику, что и хронология ISO, используемая java.time, единственной реальной проблемой является анализ и генерация объектов String в соответствии с этим конкретным форматом. Все другое поведение может быть использовано из LocalDate
,
Поскольку я не могу найти способ определить java.time.format.DateTimeFormatter
для этой цели я предлагаю написать вспомогательный класс для управления этими делами.
В идеале мы бы продлили LocalDate
класс, переопределяя его parse
а также toString
методы. И, возможно, getCenturyOffset
метод. Но LocalDate
класс помечен final
и не может быть продлен. Так что я бы создал что-то вроде этого класса, показанного ниже, оборачивая LocalDate
,
ПРЕДУПРЕЖДЕНИЕ. Используйте на свой страх и риск. Свежий код, едва запускаемый, вряд ли проверенный. Имеется в виду, например, не для использования в производстве. Использовать в соответствии с условиями лицензии ISC.
package com.example.whatever;
import java.time.LocalDate;
import java.time.ZoneId;
/**
* Wraps a 'LocalDate' to provide parsing/generating of strings in format known
* as JD Edwards date.
*
* Format is CYYDDD where C is the number of centuries from 1900, YY is the year
* within that century, and DDD is the ordinal day within the year (1-365 or
* 1-366 in Leap Year).
*
* Immutable object. Thread-safe (hopefully! No guarantees).
*
* I would rather have done this by extending the 'java.time.LocalDate' class, but that class is marked 'final'.
*
* Examples: '000001' is January 1 of 1900. '116032' is February 1, 2016.
*
* © 2016 Basil Bourque. This source code may be used according to terms of the ISC License at https://opensource.org/licenses/ISC
*
* @author Basil Bourque
*/
public class JDEdwardsLocalDate {
private LocalDate localDate = null;
private int centuryOffset;
private int yearOfCentury;
private String formatted = null;
// Static Factory method, in lieu of public constructor.
static public JDEdwardsLocalDate from ( LocalDate localDateArg ) {
return new JDEdwardsLocalDate ( localDateArg );
}
// Static Factory method, in lieu of public constructor.
static public JDEdwardsLocalDate parse ( CharSequence charSequenceArg ) {
if ( null == charSequenceArg ) {
throw new IllegalArgumentException ( "Passed CharSequence that is null. Message # 0072f897-b05f-4a0e-88d9-57cfd63a712c." );
}
if ( charSequenceArg.length () != 6 ) {
throw new IllegalArgumentException ( "Passed CharSequence that is not six characters in length. Message # eee1e134-8ec9-4c92-aff3-9296eac1a84a." );
}
String string = charSequenceArg.toString ();
// Should have all digits. Test by converting to an int.
try {
int testAsInteger = Integer.parseInt ( string );
} catch ( NumberFormatException e ) {
throw new IllegalArgumentException ( "Passed CharSequence contains non-digits. Fails to convert to an integer value. Message # 0461f0ee-b6d6-451c-8304-6ceface05332." );
}
// Validity test passed.
// Parse.
int centuryOffset = Integer.parseInt ( string.substring ( 0 , 1 ) ); // Plus/Minus from '19' (as in '1900').
int yearOfCentury = Integer.parseInt ( string.substring ( 1 , 3 ) );
int ordinalDayOfYear = Integer.parseInt ( string.substring ( 3 ) );
int centuryStart = ( ( centuryOffset + 19 ) * 100 ); // 0 -> 1900. 1 -> 2000. 2 -> 2100.
int year = ( centuryStart + yearOfCentury );
LocalDate localDate = LocalDate.ofYearDay ( year , ordinalDayOfYear );
return new JDEdwardsLocalDate ( localDate );
}
// Constructor.
private JDEdwardsLocalDate ( LocalDate localDateArg ) {
this.localDate = localDateArg;
// Calculate century offset, how many centuries plus/minus from 1900.
int year = this.localDate.getYear ();
int century = ( year / 100 );
this.yearOfCentury = ( year - ( century * 100 ) ); // example: if 2016, return 16.
this.centuryOffset = ( century - 19 );
// Format as string.
String paddedYearOfCentury = String.format ( "%02d" , this.yearOfCentury );
String paddedDayOfYear = String.format ( "%03d" , this.localDate.getDayOfYear () );
this.formatted = ( this.centuryOffset + paddedYearOfCentury + paddedDayOfYear );
}
@Override
public String toString () {
return this.formatted;
}
public LocalDate toLocalDate () {
// Returns a java.time.LocalDate which shares the same ISO chronology as a JD Edwards Date.
return this.localDate;
}
public int getDayOfYear () {
// Returns ordinal day number within the year, 1-365 inclusive or 1-366 for Leap Year.
return this.localDate.getDayOfYear();
}
public int getYear () {
// Returns a year number such as 2016.
return this.localDate.getYear();
}
public int getYearOfCentury () {
// Returns a number within 0 and 99 inclusive.
return this.yearOfCentury;
}
public int getCenturyOffset () {
// Returns 0 for 19xx dates, 1 for 20xx dates, 2 for 21xx dates, and so on.
return this.centuryOffset;
}
public static void main ( String[] args ) {
// '000001' is January 1, 1900.
JDEdwardsLocalDate jde1 = JDEdwardsLocalDate.parse ( "000001" );
System.out.println ( "'000001' = JDEdwardsLocalDate: " + jde1 + " = LocalDate: " + jde1.toLocalDate () + " Should be: January 1, 1900. " );
// '116032' is February 1, 2016.
JDEdwardsLocalDate jde2 = JDEdwardsLocalDate.parse ( "116032" );
System.out.println ( "'116032' = JDEdwardsLocalDate: " + jde2 + " = LocalDate: " + jde2.toLocalDate () + " Should be: February 1, 2016." );
// Today
LocalDate today = LocalDate.now ( ZoneId.systemDefault () );
JDEdwardsLocalDate jdeToday = JDEdwardsLocalDate.from ( today );
System.out.println ( "LocalDate.now(): " + today + " = JDEdwardsLocalDate: " + jdeToday + " to LocalDate: " + jdeToday.toLocalDate () );
}
}
Когда беги.
'000001' = JDEdwardsLocalDate: 000001 = LocalDate: 1900-01-01 Должно быть: 1 января 1900 года.
'116032' = JDEdwardsLocalDate: 116032 = LocalDate: 2016-02-01 Должно быть: 1 февраля 2016 г.
LocalDate.now (): 2016-05-09 = JDEdwardsLocalDate: 116130 до LocalDate: 2016-05-09
JD Edwards время суток
Что касается форматов времени дня JD Edwards, я искал и не смог найти никакой документации. Если вы знаете о некоторых, пожалуйста, отредактируйте свой Вопрос, чтобы добавить ссылки. Казалось, единственное упоминание времени JDE - это количество секунд с полуночи.
Если это так (отсчет с полуночи), то java.time.LocalTime
класс тебя охватил. LocalTime
может быть создан и читаться как:
- Целых секунд с начала дня (
withSecond
,ofSecondOfDay
) - Дробные секунды с начала дня, с разрешением наносекунд (
withNano
,ofNanoOfDay
)
Разрешение наносекунды означает до девяти цифр десятичной дроби. Нет проблем с шестью цифрами, которые вы упомянули. Просто сделайте математику, умножьте / разделите на 1_000L
, Просто имейте в виду, что это означает возможную потерю данных, так как вы можете усечь эти последние три цифры дроби (7-й, 8-й, 9-й цифр десятичной дроби), если LocalTime
значение пришло извне данных JD Edwards. [FYI, старые классы java.util.Date/.Calendar, а также Joda-Time ограничены разрешением в миллисекундах для трех цифр десятичной дроби.]
Не рекомендуется: Вы можете сделать какой-нибудь комбо-класс, состоящий из LocalDate
и LocalTime
, Или используйте LocalDateTime
, Ключевой вопрос - часовой пояс. Если дата-время JD Edwards всегда находится в определенном часовом поясе, таком как UTC, то имеет смысл объединить и использовать OffsetDateTime
, Но если у него нет определенного контекста часового пояса, если значения представляют собой просто нечеткое представление о дате-времени, а не об определенных точках на временной шкале, используйте LocalDateTime
так как у него нет часового пояса. Если JDE всегда в UTC, используйте OffsetDateTime
установлен в ZoneOffset.UTC
, Если вы хотите указать часовой пояс (смещение плюс правила для обработки аномалий, таких как DST), используйте ZonedDateTime
,
Рекомендуется: используйте LocalTime отдельно. Я не думаю, что вы хотите использовать мой класс JDEdwardsLocalDate в вашей бизнес-логике, особенно потому, что он не является полной реализацией, подходящей для фреймворка java.time. Я намерен использовать этот класс, чтобы немедленно преобразовать в LocalDate
когда вы встречаете дату JDE. То же самое относится к времени дня JDE, конвертировать в LocalTime
немедленно. Если их контекст всегда UTC, создайте OffsetDateTime
с UTC, а затем передать это вокруг вашей бизнес-логики. Вернитесь к дате и времени JDE только тогда, когда это необходимо (сохранение в столбце базы данных этого типа JDE или сообщение пользователю, ожидающему эту презентацию JDE).
OffsetDateTime odt = OffsetDateTime.of( myLocalDate , myLocalTime , ZoneOffset.UTC );
Если дата и время JDE подразумевают какой-то другой контекст, назначьте предполагаемый часовой пояс.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( myLocalDate , myLocalTime , zoneId );
Часовой пояс здесь имеет решающее значение. Вы должны понимать концепции в целом. Будь уверен, что LocalDate
а также LocalTime
а также LocalDateTime
не момент на временной шкале. Они не имеют конкретного значения, пока вы не настроите их на часовой пояс (или, по крайней мере, на смещение от UTC).
Моя диаграмма типов даты и времени, включенная в этот ответ, может помочь вам, если вы не знакомы с типами java.time.
И вы должны понимать значение даты и времени JDE и их использование в ваших приложениях / базах данных. Поскольку я не мог ничего найти о времени JDE, я не мог ничего узнать о намерениях JD Edwards относительно часовых поясов. Поэтому я не могу предложить что-то более конкретное.
Нет: ни Joda Time, ни Java 8 не поддерживают представления времени JD Edwards.