package com.github.lwhite1.tablesaw.columns.packeddata; import com.google.common.base.Strings; import com.google.common.primitives.Ints; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.Month; import java.time.ZoneId; import java.time.chrono.IsoChronology; import java.util.Date; /** * 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. */ private 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. */ 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) ((byte2 << 8) + (byte1 & 0xFF)); } public static LocalDate asLocalDate(int date) { if (date == Integer.MIN_VALUE) { return null; } // get first two bytes, then each of the other two byte yearByte1 = (byte) (date >> 24); byte yearByte2 = (byte) (date >> 16); return LocalDate.of( (short) ((yearByte2 << 8) + (yearByte1 & 0xFF)), (byte) (date >> 8), (byte) date); } public static byte getMonthValue(int date) { // get the third byte return (byte) (date >> 8); } public static int pack(LocalDate date) { short year = (short) date.getYear(); byte byte1 = (byte) year; byte byte2 = (byte) ((year >> 8) & 0xff); return Ints.fromBytes( byte1, byte2, (byte) date.getMonthValue(), (byte) date.getDayOfMonth()); } public static int pack(Date date) { return pack(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); } public static int pack(short yr, byte m, byte d) { byte byte1 = (byte) yr; byte byte2 = (byte) ((yr >> 8) & 0xff); return Ints.fromBytes( byte1, byte2, m, d); } public static String toDateString(int date) { if (date == Integer.MIN_VALUE) { return "NA"; } // get first two bytes, then each of the other two byte yearByte1 = (byte) (date >> 24); byte yearByte2 = (byte) (date >> 16); return (short) ((yearByte2 << 8) + (yearByte1 & 0xFF)) + "-" + Strings.padStart(Byte.toString((byte) (date >> 8)), 2, '0') + "-" + Strings.padStart(Byte.toString((byte) date), 2, '0'); } 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; } } public int lengthOfYear(int packedDate) { return (isLeapYear(packedDate) ? 366 : 365); } /** * Returns the epoch day in a form consistent with the java standard */ public static long toEpochDay(int packedDate) { long y = PackedLocalDate.getYear(packedDate); long m = PackedLocalDate.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) { //TODO(lwhite): This is throwing an exception java.lang.NoSuchMethodError even tho the jdk version seems correct int dow0 = (int) Math.floorMod(toEpochDay(packedDate) + 3, 7); return DayOfWeek.of(dow0 + 1); } 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: case DECEMBER: return 4; } throw new RuntimeException("Failed to extract quarter from packedDate"); } 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 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 isSunday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.SUNDAY; } public static boolean isMonday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.MONDAY; } public static boolean isTuesday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.TUESDAY; } public static boolean isWednesday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.WEDNESDAY; } public static boolean isThursday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.THURSDAY; } public static boolean isFriday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.FRIDAY; } public static boolean isSaturday(int packedDate) { DayOfWeek dayOfWeek = getDayOfWeek(packedDate); return dayOfWeek == DayOfWeek.SATURDAY; } public static boolean isFirstDayOfMonth(int packedDate) { return getDayOfMonth(packedDate) == 1; } public static boolean isInJanuary(int packedDate) { return getMonth(packedDate) == Month.JANUARY; } public static boolean isInFebruary(int packedDate) { return getMonth(packedDate) == Month.FEBRUARY; } public static boolean isInMarch(int packedDate) { return getMonth(packedDate) == Month.MARCH; } public static boolean isInApril(int packedDate) { return getMonth(packedDate) == Month.APRIL; } public static boolean isInMay(int packedDate) { return getMonth(packedDate) == Month.MAY; } public static boolean isInJune(int packedDate) { return getMonth(packedDate) == Month.JUNE; } public static boolean isInJuly(int packedDate) { return getMonth(packedDate) == Month.JULY; } public static boolean isInAugust(int packedDate) { return getMonth(packedDate) == Month.AUGUST; } public static boolean isInSeptember(int packedDate) { return getMonth(packedDate) == Month.SEPTEMBER; } public static boolean isInOctober(int packedDate) { return getMonth(packedDate) == Month.OCTOBER; } public static boolean isInNovember(int packedDate) { return getMonth(packedDate) == Month.NOVEMBER; } public static boolean isInDecember(int packedDate) { return getMonth(packedDate) == Month.DECEMBER; } public static boolean isLastDayOfMonth(int packedDate) { return getDayOfMonth(packedDate) == lengthOfMonth(packedDate); } public static boolean isInYear(int next, int year) { return getYear(next) == year; } }