Skip to content

Instantly share code, notes, and snippets.

@dmikurube
Last active April 20, 2024 15:35
Show Gist options
  • Save dmikurube/3ef5f0c204be579360f1474352a476f2 to your computer and use it in GitHub Desktop.
Save dmikurube/3ef5f0c204be579360f1474352a476f2 to your computer and use it in GitHub Desktop.
DateTimeFormatter
plugins {
id "java"
id "application"
}
configurations {
compileClasspath.resolutionStrategy.activateDependencyLocking()
runtimeClasspath.resolutionStrategy.activateDependencyLocking()
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked"
options.encoding = "UTF-8"
}
sourceSets {
main {
java {
srcDir '.'
}
}
}
dependencies {
}
application {
mainClass = "Main"
}
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.IsoFields;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;
import java.util.Map;
public class DayOfQuarter implements TemporalField {
@Override
public TemporalUnit getBaseUnit() {
return ChronoUnit.DAYS;
}
@Override
public TemporalUnit getRangeUnit() {
return IsoFields.QUARTER_YEARS;
}
@Override
public ValueRange range() {
return ValueRange.of(1, 90, 92);
}
@Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(ChronoField.DAY_OF_YEAR) && temporal.isSupported(ChronoField.MONTH_OF_YEAR) &&
temporal.isSupported(ChronoField.YEAR) && isIso(temporal);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");
}
long qoy = temporal.getLong(IsoFields.QUARTER_OF_YEAR);
if (qoy == 1) {
long year = temporal.getLong(ChronoField.YEAR);
return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
} else if (qoy == 2) {
return ValueRange.of(1, 91);
} else if (qoy == 3 || qoy == 4) {
return ValueRange.of(1, 92);
} // else value not from 1 to 4, so drop through
return range();
}
@Override
public long getFrom(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");
}
int doy = temporal.get(ChronoField.DAY_OF_YEAR);
int moy = temporal.get(ChronoField.MONTH_OF_YEAR);
long year = temporal.getLong(ChronoField.YEAR);
return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)];
}
@SuppressWarnings("unchecked")
@Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
// calls getFrom() to check if supported
long curValue = getFrom(temporal);
range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check
return (R) temporal.with(ChronoField.DAY_OF_YEAR, temporal.getLong(ChronoField.DAY_OF_YEAR) + (newValue - curValue));
}
@Override
public boolean isTimeBased() {
return false;
}
@Override
public boolean isDateBased() {
return true;
}
@Override
public ChronoLocalDate resolve(
Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
Long yearLong = fieldValues.get(ChronoField.YEAR);
Long qoyLong = fieldValues.get(IsoFields.QUARTER_OF_YEAR);
if (yearLong == null || qoyLong == null) {
return null;
}
int y = ChronoField.YEAR.checkValidIntValue(yearLong); // always validate
long doq = fieldValues.get(IsoFields.DAY_OF_QUARTER);
ensureIso(partialTemporal);
LocalDate date;
if (resolverStyle == ResolverStyle.LENIENT) {
date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoyLong, 1), 3));
doq = Math.subtractExact(doq, 1);
} else {
int qoy = IsoFields.QUARTER_OF_YEAR.range().checkValidIntValue(qoyLong, IsoFields.QUARTER_OF_YEAR); // validated
date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);
if (doq < 1 || doq > 90) {
if (resolverStyle == ResolverStyle.STRICT) {
rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range
} else { // SMART
range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter
}
}
doq--;
}
fieldValues.remove(this);
fieldValues.remove(ChronoField.YEAR);
fieldValues.remove(IsoFields.QUARTER_OF_YEAR);
return date.plusDays(doq);
}
@Override
public String toString() {
return "DayOfQuarter";
}
private static boolean isIso(TemporalAccessor temporal) {
return Chronology.from(temporal).equals(IsoChronology.INSTANCE);
}
private static void ensureIso(TemporalAccessor temporal) {
if (isIso(temporal) == false) {
throw new DateTimeException("Resolve requires IsoChronology");
}
}
private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274};
}
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
public class Main {
public static void main(final String[] args) {
padLiteral();
}
private static void padLiteral() {
final DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
builder.appendValue(ChronoField.DAY_OF_MONTH, 2);
builder.padNext(10, '0');
builder.appendLiteral("foo");
final DateTimeFormatter formatter = builder.toFormatter();
System.out.println(formatter.format(OffsetDateTime.now()));
final TemporalAccessor accessor = formatter.parse("190000000foo");
System.out.println(accessor.getLong(ChronoField.DAY_OF_MONTH));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment