init research

This commit is contained in:
2026-02-08 11:20:43 -10:00
commit bdf064f54d
3041 changed files with 1592200 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
package tech.v3.dataset;
import clojure.lang.RT;
import clojure.lang.IDeref;
import java.util.function.LongConsumer;
import ham_fisted.ArrayLists;
import ham_fisted.IMutList;
import org.roaringbitmap.RoaringBitmap;
public class ByteValidity {
public static int trimIndexes(int[] indexes, int nIndexes, long maxIdx) {
while(nIndexes > 0) {
if(Integer.toUnsignedLong(indexes[nIndexes-1]) >= maxIdx)
--nIndexes;
else
break;
}
return nIndexes;
}
public static abstract class ValidityBase implements LongConsumer, IDeref {
long nElems;
int idx;
int nIndexes;
int[] indexes;
public ValidityBase(long nElems, long maxIndexes) {
this.nElems = nElems;
indexes = new int[(int)maxIndexes];
nIndexes = 0;
idx = 0;
}
}
public static class ValidityIndexReducer implements LongConsumer, IDeref {
IMutList indexes;
public final long maxIdx;
int idx;
public ValidityIndexReducer(IMutList indexes, long maxIdx) {
this.indexes = indexes;
this.maxIdx = maxIdx;
this.idx = 0;
}
public int trimIndexes() {
int nIndexes = indexes.size();
//empty loop intentional
for(;nIndexes > 0 && indexes.getLong(nIndexes-1) >= maxIdx; --nIndexes);
return nIndexes;
}
public void accept(long value) {
if(value != 0) {
int intVal = (int)value;
int offset = idx * 8;
if( (intVal & 1) == 1) indexes.addLong(offset);
if( (intVal & 2) == 2) indexes.addLong(offset+1);
if( (intVal & 4) == 4) indexes.addLong(offset+2);
if( (intVal & 8) == 8) indexes.addLong(offset+3);
if( (intVal & 16) == 16) indexes.addLong(offset+4);
if( (intVal & 32) == 32) indexes.addLong(offset+5);
if( (intVal & 64) == 64) indexes.addLong(offset+6);
if( (intVal & 128) == 128) indexes.addLong(offset+7);
}
++idx;
}
public Object deref() {
return indexes.subList(0, trimIndexes());
}
}
public static class MissingIndexReducer extends ValidityBase {
public MissingIndexReducer(long nElems, long maxIndexes) {
super(nElems, maxIndexes);
}
public void accept(long value) {
if(value != -1) {
int intVal = (int)value;
int offset = idx * 8;
if( (intVal & 1) != 1) indexes[nIndexes++] = offset;
if( (intVal & 2) != 2) indexes[nIndexes++] = offset+1;
if( (intVal & 4) != 4) indexes[nIndexes++] = offset+2;
if( (intVal & 8) != 8) indexes[nIndexes++] = offset+3;
if( (intVal & 16) != 16) indexes[nIndexes++] = offset+4;
if( (intVal & 32) != 32) indexes[nIndexes++] = offset+5;
if( (intVal & 64) != 64) indexes[nIndexes++] = offset+6;
if( (intVal & 128) != 128) indexes[nIndexes++] = offset+7;
}
++idx;
}
public Object deref() {
RoaringBitmap rb = new RoaringBitmap();
rb.addN(indexes, 0, trimIndexes(indexes, nIndexes, nElems));
return rb;
}
}
}
+65
View File
@@ -0,0 +1,65 @@
package tech.v3.dataset;
import tech.v3.datatype.IndexConsumer;
import tech.v3.datatype.ECount;
import ham_fisted.Reducible;
import ham_fisted.IMutList;
import org.roaringbitmap.RoaringBitmap;
import clojure.lang.IDeref;
import clojure.lang.IFn;
import clojure.lang.Keyword;
import clojure.lang.PersistentArrayMap;
public class IntColParser
implements IDeref, PParser, ECount {
public final IndexConsumer data;
public final RoaringBitmap missing;
public final Object colname;
long lastidx;
public IntColParser(IFn rangeFn, IMutList dlist, Object colname) {
data = new IndexConsumer(rangeFn, dlist);
missing = new RoaringBitmap();
this.colname = colname;
}
public long lsize() { return lastidx; }
public void addMissing(long idx) {
if(lastidx < idx) {
missing.add(lastidx, idx);
}
lastidx = idx+1;
}
public void addValue(long idx, Object val) {
addMissing(idx);
if (val instanceof Long)
data.accept((Long)val);
else if (val instanceof Integer)
data.accept((Integer)val);
else if (val instanceof Short)
data.accept((Short)val);
else if (val instanceof Byte)
data.accept((Byte)val);
else if (val == null)
missing.add((int)idx);
else
throw new RuntimeException("Value " + String.valueOf(val) + " is not an integer value");
}
public Object deref() {
return new PersistentArrayMap(new Object[] {
Keyword.intern("tech.v3.dataset", "data"), data.deref(),
Keyword.intern("tech.v3.dataset", "missing"), missing,
Keyword.intern("tech.v3.dataset", "name"), colname});
}
public Object finalize(long rowcount) {
addMissing(rowcount);
return deref();
}
}
+193
View File
@@ -0,0 +1,193 @@
/*
* Copyright (c) 2010-2020 Haifeng Li. All rights reserved.
*
* Smile is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Smile is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Smile. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.v3.dataset;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import org.apache.parquet.io.InputFile;
import org.apache.parquet.io.SeekableInputStream;
/**
* Parquet InputFile with a local java.nio.Path.
* Adapted from https://github.com/tideworks/arvo2parquet
*
* @author Haifeng Li
* @hacks Chris Nuernberger
*/
public class LocalInputFile implements InputFile {
/** Local file object. */
private final RandomAccessFile input;
/**
* Constructor.
* @param path the input file path.
* @throws FileNotFoundException when file cannot be found.
*/
public LocalInputFile(Path path) throws FileNotFoundException {
input = new RandomAccessFile(path.toFile(), "r");
}
@Override
public long getLength() throws IOException {
return input.length();
}
@Override
public SeekableInputStream newStream() throws IOException {
return new SeekableInputStream() {
private final byte[] page = new byte[8192];
private long markPos = 0;
@Override
public int read() throws IOException {
return input.read();
}
@Override
public int read(byte[] b) throws IOException {
return input.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return input.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
final long savPos = input.getFilePointer();
final long amtLeft = input.length() - savPos;
n = Math.min(n, amtLeft);
final long newPos = savPos + n;
input.seek(newPos);
final long curPos = input.getFilePointer();
return curPos - savPos;
}
@Override
public int available() {
return 0;
}
@Override
public void close() throws IOException {
input.close();
}
@SuppressWarnings({"unchecked", "unused", "UnusedReturnValue"})
private <T extends Throwable, R> R uncheckedExceptionThrow(Throwable t) throws T {
throw (T) t;
}
@Override
public synchronized void mark(int readlimit) {
try {
markPos = input.getFilePointer();
} catch (IOException e) {
uncheckedExceptionThrow(e);
}
}
@Override
public synchronized void reset() throws IOException {
input.seek(markPos);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public long getPos() throws IOException {
return input.getFilePointer();
}
@Override
public void seek(long l) throws IOException {
input.seek(l);
}
@Override
public void readFully(byte[] bytes) throws IOException {
input.readFully(bytes);
}
@Override
public void readFully(byte[] bytes, int i, int i1) throws IOException {
input.readFully(bytes, i, i1);
}
@Override
public int read(ByteBuffer byteBuffer) throws IOException {
return readDirectBuffer(byteBuffer, page, input::read);
}
@Override
public void readFully(ByteBuffer byteBuffer) throws IOException {
readFullyDirectBuffer(byteBuffer, page, input::read);
}
};
}
private interface ByteBufferReader {
int read(byte[] b, int off, int len) throws IOException;
}
private int readDirectBuffer(ByteBuffer byteBuffer, byte[] page, ByteBufferReader reader) throws IOException {
// copy all the bytes that return immediately, stopping at the first
// read that doesn't return a full buffer.
int nextReadLength = Math.min(byteBuffer.remaining(), page.length);
int totalBytesRead = 0;
int bytesRead;
while ((bytesRead = reader.read(page, 0, nextReadLength)) == page.length) {
byteBuffer.put(page);
totalBytesRead += bytesRead;
nextReadLength = Math.min(byteBuffer.remaining(), page.length);
}
if (bytesRead < 0) {
// return -1 if nothing was read
return totalBytesRead == 0 ? -1 : totalBytesRead;
} else {
// copy the last partial buffer
byteBuffer.put(page, 0, bytesRead);
totalBytesRead += bytesRead;
return totalBytesRead;
}
}
private static void readFullyDirectBuffer(ByteBuffer byteBuffer, byte[] page, ByteBufferReader reader) throws IOException {
int nextReadLength = Math.min(byteBuffer.remaining(), page.length);
int bytesRead = 0;
while (nextReadLength > 0 && (bytesRead = reader.read(page, 0, nextReadLength)) >= 0) {
byteBuffer.put(page, 0, bytesRead);
nextReadLength = Math.min(byteBuffer.remaining(), page.length);
}
if (bytesRead < 0 && byteBuffer.remaining() > 0) {
throw new EOFException("Reached the end of stream with " + byteBuffer.remaining() + " bytes left to read");
}
}
}
@@ -0,0 +1,22 @@
package tech.v3.dataset;
import java.io.InputStream;
import java.io.IOException;
public class NoCloseInputStream extends InputStream {
public final InputStream stream;
public NoCloseInputStream(InputStream _stream) {
stream = _stream;
}
public int available() throws IOException { return stream.available(); }
//Explicitly do not forward close call
public void close(){}
public void mark(int maxBytes) { stream.mark(maxBytes); }
public boolean markSupported() { return stream.markSupported(); }
public int read() throws IOException { return stream.read(); }
public int read(byte[] data) throws IOException { return stream.read(data); }
public int read(byte[] data, int off, int len) throws IOException { return stream.read(data,off,len); }
public void reset() throws IOException { stream.reset(); }
public long skip(long n) throws IOException { return stream.skip(n); }
}
@@ -0,0 +1,20 @@
package tech.v3.dataset;
import java.io.OutputStream;
import java.io.IOException;
public class NoCloseOutputStream extends OutputStream {
public final OutputStream stream;
public NoCloseOutputStream(OutputStream os) {
stream = os;
}
public void close() throws IOException {}
public void fluse() throws IOException { stream.flush(); }
public void write(byte[] b) throws IOException { stream.write(b); }
public void write(byte[] b, int off, int len) throws IOException {
stream.write(b,off,len);
}
public void write(int b) throws IOException { stream.write(b); }
}
+7
View File
@@ -0,0 +1,7 @@
package tech.v3.dataset;
public interface PParser {
public void addValue(long idx, Object value);
public Object finalize(long rowcount);
}
@@ -0,0 +1,55 @@
package tech.v3.dataset;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.io.api.RecordConsumer;
import org.apache.parquet.hadoop.ParquetWriter;
import org.apache.parquet.hadoop.api.WriteSupport;
import org.apache.hadoop.conf.Configuration;
import org.apache.parquet.io.OutputFile;
import clojure.lang.IFn;
import java.util.Map;
public class ParquetRowWriter extends WriteSupport<Long>
{
public final IFn rowWriter;
public final MessageType schema;
public final Map<String,String> metadata;
public RecordConsumer consumer;
public Object dataset;
public ParquetRowWriter(IFn _writer, MessageType _schema, Map<String,String> _meta) {
rowWriter = _writer;
schema = _schema;
metadata = _meta;
consumer = null;
//Outside forces must set dataset
dataset = null;
}
@Override
public WriteContext init(Configuration configuration) {
return new WriteContext( schema, metadata );
}
@Override
public void prepareForWrite(RecordConsumer recordConsumer) {
consumer = recordConsumer;
}
@Override
public void write(Long record) {
rowWriter.invoke(dataset,record,consumer);
}
public static class WriterBuilder extends ParquetWriter.Builder<Long,WriterBuilder>
{
public final ParquetRowWriter writer;
public WriterBuilder(OutputFile outf, ParquetRowWriter _writer) {
super(outf);
writer = _writer;
}
public WriterBuilder self() { return this; }
protected WriteSupport<Long> getWriteSupport(Configuration conf) {
return writer;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
package tech.v3.dataset;
import clojure.lang.Keyword;
import java.lang.Iterable;
import java.lang.AutoCloseable;
public class Spreadsheet
{
public interface Workbook extends Iterable, AutoCloseable
{
}
public interface Sheet extends Iterable
{
public String name();
public String id();
public String stableId();
}
public interface Row extends Iterable
{
public int getRowNum();
}
public interface Cell
{
boolean missing();
int getColumnNum();
Object value();
double doubleValue();
boolean boolValue();
}
}
+32
View File
@@ -0,0 +1,32 @@
package tech.v3.dataset;
import java.util.Objects;
public class Text implements Comparable
{
public final String text;
public Text(String data) {
Objects.requireNonNull(data);
text = data;
}
public String toString() { return text; }
public int hashCode() { return text.hashCode(); }
public boolean equals(Object other) {
String strData = null;
if (other instanceof String) {
strData = (String)other;
} else if (other instanceof Text) {
strData = ((Text)other).text;
}
return text.equals(strData);
}
public int compareTo(Object other) {
String strData = null;
if (other instanceof String) {
strData = (String)other;
} else if (other instanceof Text) {
strData = ((Text)other).text;
}
return text.compareTo(strData);
}
}
@@ -0,0 +1,517 @@
/*
* Licensed 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.
*/
/** This file comes mainly from the tablesaw java project:
https://github.com/jtablesaw
*/
package tech.v3.datatype;
import static java.time.DayOfWeek.FRIDAY;
import static java.time.DayOfWeek.MONDAY;
import static java.time.DayOfWeek.SATURDAY;
import static java.time.DayOfWeek.SUNDAY;
import static java.time.DayOfWeek.THURSDAY;
import static java.time.DayOfWeek.TUESDAY;
import static java.time.DayOfWeek.WEDNESDAY;
import static java.time.Month.APRIL;
import static java.time.Month.AUGUST;
import static java.time.Month.DECEMBER;
import static java.time.Month.FEBRUARY;
import static java.time.Month.JANUARY;
import static java.time.Month.JULY;
import static java.time.Month.JUNE;
import static java.time.Month.MARCH;
import static java.time.Month.MAY;
import static java.time.Month.NOVEMBER;
import static java.time.Month.OCTOBER;
import static java.time.Month.SEPTEMBER;
import static java.time.temporal.ChronoField.EPOCH_DAY;
import static java.time.temporal.ChronoField.YEAR;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.chrono.IsoChronology;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.Locale;
/**
* A short localdate packed into a single int value. It uses a short for year so the range is about
* +-30,000 years
*
* <p>The bytes are packed into the int as: First two bytes: short (year) next byte (month of year)
* last byte (day of month)
*/
public class PackedLocalDate {
/** The number of days in a 400 year cycle. */
public static final int DAYS_PER_CYCLE = 146097;
/**
* The number of days from year zero to year 1970. There are five 400 year cycles from year zero
* to 2000. There are 7 leap years from 1970 to 2000.
*/
public static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
public static byte getDayOfMonth(int date) {
return (byte) date; // last byte
}
public static short getYear(int date) {
// get first two bytes, then convert to a short
byte byte1 = (byte) (date >> 24);
byte byte2 = (byte) (date >> 16);
return (short) ((byte1 << 8) + (byte2 & 0xFF));
}
public static LocalDate asLocalDate(int date) {
return LocalDate.of(getYear(date), getMonthValue(date), getDayOfMonth(date));
}
public static byte getMonthValue(int date) {
// get the third byte
return (byte) (date >> 8);
}
public static int pack(LocalDate date) {
return pack((short) date.getYear(), (byte) date.getMonthValue(), (byte) date.getDayOfMonth());
}
public static int pack(short yr, byte m, byte d) {
byte byte1 = (byte) ((yr >> 8) & 0xff);
byte byte2 = (byte) yr;
return ByteConversions.intFromBytesBE(byte1, byte2, m, d);
}
public static int pack(int yr, int m, int d) {
byte byte1 = (byte) ((yr >> 8) & 0xff);
byte byte2 = (byte) yr;
return ByteConversions.intFromBytesBE(byte1, byte2, (byte) m, (byte) d);
}
public static int getDayOfYear(int packedDate) {
return getMonth(packedDate).firstDayOfYear(isLeapYear(packedDate))
+ getDayOfMonth(packedDate)
- 1;
}
public static boolean isLeapYear(int packedDate) {
return IsoChronology.INSTANCE.isLeapYear(getYear(packedDate));
}
public static Month getMonth(int packedDate) {
return Month.of(getMonthValue(packedDate));
}
public static int lengthOfMonth(int packedDate) {
switch (getMonthValue(packedDate)) {
case 2:
return (isLeapYear(packedDate) ? 29 : 28);
case 4:
case 6:
case 9:
case 11:
return 30;
default:
return 31;
}
}
/** Returns the epoch day in a form consistent with the java standard */
public static long toEpochDay(int packedDate) {
long y = getYear(packedDate);
long m = getMonthValue(packedDate);
long total = 0;
total += 365 * y;
if (y >= 0) {
total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
} else {
total -= y / -4 - y / -100 + y / -400;
}
total += ((367 * m - 362) / 12);
total += getDayOfMonth(packedDate) - 1;
if (m > 2) {
total--;
if (!isLeapYear(packedDate)) {
total--;
}
}
return total - DAYS_0000_TO_1970;
}
public static DayOfWeek getDayOfWeek(int packedDate) {
int dow0 = Math.floorMod((int) toEpochDay(packedDate) + 3, 7);
return DayOfWeek.of(dow0 + 1);
}
/**
* Returns the quarter of the year of the given date as an int from 1 to 4, or -1, if the argument
* is the MISSING_VALUE for DateColumn
*/
public static int getQuarter(int packedDate) {
Month month = getMonth(packedDate);
switch (month) {
case JANUARY:
case FEBRUARY:
case MARCH:
return 1;
case APRIL:
case MAY:
case JUNE:
return 2;
case JULY:
case AUGUST:
case SEPTEMBER:
return 3;
case OCTOBER:
case NOVEMBER:
default: // must be december
return 4;
}
}
public static boolean isInQ1(int packedDate) {
return getQuarter(packedDate) == 1;
}
public static boolean isInQ2(int packedDate) {
return getQuarter(packedDate) == 2;
}
public static boolean isInQ3(int packedDate) {
return getQuarter(packedDate) == 3;
}
public static boolean isInQ4(int packedDate) {
return getQuarter(packedDate) == 4;
}
public static boolean isAfter(int packedDate, int value) {
return packedDate > value;
}
public static boolean isEqualTo(int packedDate, int value) {
return packedDate == value;
}
public static boolean isBefore(int packedDate, int value) {
return packedDate < value;
}
public static boolean isOnOrBefore(int packedDate, int value) {
return packedDate <= value;
}
public static boolean isOnOrAfter(int packedDate, int value) {
return packedDate >= value;
}
public static boolean isDayOfWeek(int packedDate, DayOfWeek dayOfWeek) {
DayOfWeek dow = getDayOfWeek(packedDate);
return dayOfWeek == dow;
}
public static boolean isSunday(int packedDate) {
return isDayOfWeek(packedDate, SUNDAY);
}
public static boolean isMonday(int packedDate) {
return isDayOfWeek(packedDate, MONDAY);
}
public static boolean isTuesday(int packedDate) {
return isDayOfWeek(packedDate, TUESDAY);
}
public static boolean isWednesday(int packedDate) {
return isDayOfWeek(packedDate, WEDNESDAY);
}
public static boolean isThursday(int packedDate) {
return isDayOfWeek(packedDate, THURSDAY);
}
public static boolean isFriday(int packedDate) {
return isDayOfWeek(packedDate, FRIDAY);
}
public static boolean isSaturday(int packedDate) {
return isDayOfWeek(packedDate, SATURDAY);
}
public static boolean isFirstDayOfMonth(int packedDate) {
return getDayOfMonth(packedDate) == 1;
}
public static boolean isInJanuary(int packedDate) {
return getMonth(packedDate) == JANUARY;
}
public static boolean isInFebruary(int packedDate) {
return getMonth(packedDate) == FEBRUARY;
}
public static boolean isInMarch(int packedDate) {
return getMonth(packedDate) == MARCH;
}
public static boolean isInApril(int packedDate) {
return getMonth(packedDate) == APRIL;
}
public static boolean isInMay(int packedDate) {
return getMonth(packedDate) == MAY;
}
public static boolean isInJune(int packedDate) {
return getMonth(packedDate) == JUNE;
}
public static boolean isInJuly(int packedDate) {
return getMonth(packedDate) == JULY;
}
public static boolean isInAugust(int packedDate) {
return getMonth(packedDate) == AUGUST;
}
public static boolean isInSeptember(int packedDate) {
return getMonth(packedDate) == SEPTEMBER;
}
public static boolean isInOctober(int packedDate) {
return getMonth(packedDate) == OCTOBER;
}
public static boolean isInNovember(int packedDate) {
return getMonth(packedDate) == NOVEMBER;
}
public static boolean isInDecember(int packedDate) {
return getMonth(packedDate) == DECEMBER;
}
public static boolean isLastDayOfMonth(int packedDate) {
return getDayOfMonth(packedDate) == lengthOfMonth(packedDate);
}
public static int withDayOfMonth(int dayOfMonth, int packedDate) {
byte d = (byte) dayOfMonth;
byte m = getMonthValue(packedDate);
short y = getYear(packedDate);
return pack(y, m, d);
}
public static int withMonth(int month, int packedDate) {
byte day = getDayOfMonth(packedDate);
byte _month = (byte) month;
short year = getYear(packedDate);
return pack(year, _month, day);
}
public static int withYear(int year, int packedDate) {
byte day = getDayOfMonth(packedDate);
byte month = getMonthValue(packedDate);
short _year = (short) year;
return pack(_year, month, day);
}
public static int plusYears(int yearsToAdd, int packedDate) {
if (yearsToAdd == 0) {
return packedDate;
}
byte d = getDayOfMonth(packedDate);
byte m = getMonthValue(packedDate);
short y = getYear(packedDate);
int newYear = YEAR.checkValidIntValue(yearsToAdd + y);
return resolvePreviousValid(newYear, m, d);
}
public static int minusYears(int years, int packedDate) {
return plusYears(-years, packedDate);
}
public static int plusMonths(int months, int packedDate) {
if (months == 0) {
return packedDate;
}
byte d = getDayOfMonth(packedDate);
byte m = getMonthValue(packedDate);
short y = getYear(packedDate);
long monthCount = y * 12L + (m - 1);
long calcMonths = monthCount + months;
int newYear = YEAR.checkValidIntValue(Math.floorDiv((int) calcMonths, 12));
int newMonth = Math.floorMod((int) calcMonths, 12) + 1;
return resolvePreviousValid(newYear, newMonth, d);
}
public static int minusMonths(int months, int packedDate) {
return plusMonths(-months, packedDate);
}
public static int plusWeeks(int valueToAdd, int packedDate) {
return plusDays(valueToAdd * 7, packedDate);
}
public static int minusWeeks(int valueToSubtract, int packedDate) {
return minusDays(valueToSubtract * 7, packedDate);
}
public static int plusDays(int days, int packedDate) {
if (days == 0) {
return packedDate;
}
byte d = getDayOfMonth(packedDate);
byte m = getMonthValue(packedDate);
short y = getYear(packedDate);
int dom = days + d;
if (dom > 0) {
if (dom <= 28) {
return pack(y, m, (byte) dom);
} else if (dom <= 59) { // 59th Jan is 28th Feb, 59th Feb is 31st Mar
long monthLen = lengthOfMonth(packedDate);
if (dom <= monthLen) {
return pack(y, m, (byte) dom);
} else if (m < 12) {
return pack(y, (byte) (m + 1), (byte) (dom - monthLen));
} else {
YEAR.checkValidValue((long) y + 1);
return pack((short) (y + 1), (byte) 1, (byte) (dom - monthLen));
}
}
}
long mjDay = Math.addExact(toEpochDay(packedDate), days);
return ofEpochDay(mjDay);
}
public static int minusDays(int days, int packedDate) {
return plusDays(-days, packedDate);
}
public static boolean isInYear(int next, int year) {
return getYear(next) == year;
}
public static int lengthOfYear(int packedDate) {
return (isLeapYear(packedDate) ? 366 : 365);
}
public static int resolvePreviousValid(int year, int month, int day) {
int dayResult = day;
switch (month) {
case 2:
dayResult = Math.min(day, IsoChronology.INSTANCE.isLeapYear(year) ? 29 : 28);
break;
case 4:
case 6:
case 9:
case 11:
dayResult = Math.min(day, 30);
break;
}
return pack((short) year, (byte) month, (byte) dayResult);
}
public static int getWeekOfYear(int packedDateTime) {
LocalDate date = asLocalDate(packedDateTime);
if (date == null) {
throw new IllegalArgumentException("Cannot get week of year for missing value");
}
TemporalField woy = WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear();
return date.get(woy);
}
public static int ofEpochDay(long epochDay) {
EPOCH_DAY.checkValidValue(epochDay);
long zeroDay = epochDay + DAYS_0000_TO_1970;
// find the march-based year
zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle
long adjust = 0;
if (zeroDay < 0) {
// adjust negative years to positive for calculation
long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
adjust = adjustCycles * 400;
zeroDay += -adjustCycles * DAYS_PER_CYCLE;
}
long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
if (doyEst < 0) {
// fix estimate
yearEst--;
doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
}
yearEst += adjust; // reset any negative year
int marchDoy0 = (int) doyEst;
// convert march-based values back to january-based
int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
int month = (marchMonth0 + 2) % 12 + 1;
int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
yearEst += marchMonth0 / 10;
// check year now we are certain it is correct
int year = YEAR.checkValidIntValue(yearEst);
return pack((short) year, (byte) month, (byte) dom);
}
public static int plus(int valueToAdd, ChronoUnit unit, int packedDate) {
switch (unit) {
case YEARS:
return plusYears(valueToAdd, packedDate);
case MONTHS:
return plusMonths(valueToAdd, packedDate);
case WEEKS:
return plusWeeks(valueToAdd, packedDate);
case DAYS:
return plusDays(valueToAdd, packedDate);
default:
throw new IllegalArgumentException("Unsupported Temporal Unit");
}
}
public static int minus(int valueToAdd, ChronoUnit unit, int packedDate) {
return plus(-valueToAdd, unit, packedDate);
}
public static int daysUntil(int packedDateEnd, int packedDateStart) {
return (int) (toEpochDay(packedDateEnd) - toEpochDay(packedDateStart));
}
public static int weeksUntil(int packedDateEnd, int packedDateStart) {
return (int) (toEpochDay(packedDateEnd) - toEpochDay(packedDateStart)) / 7;
}
public static int monthsUntil(int packedDateEnd, int packedDateStart) {
int start = getMonthInternal(packedDateStart) * 32 + getDayOfMonth(packedDateStart);
int end = getMonthInternal(packedDateEnd) * 32 + getDayOfMonth(packedDateEnd);
return (end - start) / 32;
}
public static int yearsUntil(int packedDateEnd, int packedDateStart) {
return monthsUntil(packedDateEnd, packedDateStart) / 12;
}
public static int getMonthInternal(int packedDate) {
return (getYear(packedDate) * 12 + getMonthValue(packedDate) - 1);
}
}