package java.time; import java.time.chrono.ChronoLocalDate; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Objects; public class LocalDate { private final int year; private final short month; private final short day; private static final int DAYS_PER_CYCLE = 146097; static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L); public static final int YEAR_MAX_VALUE = 999_999_999; public static final int YEAR_MIN_VALUE = -999_999_999; static final int HOURS_PER_DAY = 24; static final int MINUTES_PER_HOUR = 60; static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY; static final int SECONDS_PER_MINUTE = 60; static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; public static final LocalDate MAX = LocalDate.of(YEAR_MAX_VALUE, 12, 31); public static final LocalDate MIN = LocalDate.of(YEAR_MIN_VALUE, 1, 1); private LocalDate(int year, int month, int dayOfMonth) { this.year = year; this.month = (short) month; this.day = (short) dayOfMonth; } public static LocalDate of(int year, int month, int dayOfMonth) { return new LocalDate(year, month, dayOfMonth); } public static LocalDate parse(String text) { //ie '2011-12-03' try { if(text.trim().length() == 10) { String trimmed = text.trim(); int year = Integer.valueOf(trimmed.substring(0,4)); int month = Integer.valueOf(trimmed.substring(5,7)); int dayOfMonth = Integer.valueOf(trimmed.substring(8,10)); return of(year, month, dayOfMonth); } }catch(Exception e){ } throw new DateTimeParseException("Unable to parse:" + text); } public static LocalDate now(Clock clock) { final Instant now = clock.instant(); // called once ZoneOffset offset = clock.getZone().getRules().getOffset(now); long epochSec = now.getEpochSecond() + offset.getTotalSeconds(); // overflow caught later long epochDay = Math.floorDiv(epochSec, SECONDS_PER_DAY); return LocalDate.ofEpochDay(epochDay); } public static LocalDate ofEpochDay(long 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; int year = (int)yearEst; return new LocalDate(year, month, dom); } public static LocalDate now() { return now(Clock.systemDefaultZone()); } public static boolean isLeapYear(long prolepticYear) { return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0); } public long toEpochDay() { long y = year; long m = month; 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 += day - 1; if (m > 2) { total--; if (isLeapYear(year) == false) { total--; } } return total - DAYS_0000_TO_1970; } private static LocalDate resolvePreviousValid(int year, int month, int day) { switch (month) { case 2: day = Math.min(day, isLeapYear(year) ? 29 : 28); break; case 4: case 6: case 9: case 11: day = Math.min(day, 30); break; } return new LocalDate(year, month, day); } public LocalDate plusMonths(long monthsToAdd) { if (monthsToAdd == 0) { return this; } long monthCount = year * 12L + (month - 1); long calcMonths = monthCount + monthsToAdd; // safe overflow int newYear = (int)Math.floorDiv(calcMonths, 12); int newMonth = (int)Math.floorMod(calcMonths, 12) + 1; return resolvePreviousValid(newYear, newMonth, day); } public boolean isBefore(LocalDate other) { return compareTo0((LocalDate) other) < 0; } public boolean isAfter(LocalDate other) { return compareTo0((LocalDate) other) > 0; } public LocalDate plusWeeks(long weeksToAdd) { return null; } public LocalDate plusYears(long yearsToAdd) { return null; } private int compareTo0(LocalDate otherDate) { int cmp = (year - otherDate.year); if (cmp == 0) { cmp = (month - otherDate.month); if (cmp == 0) { cmp = (day - otherDate.day); } } return cmp; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof LocalDate) { return compareTo0((LocalDate) obj) == 0; } return false; } @Override public int hashCode() { int yearValue = year; int monthValue = month; int dayValue = day; return (yearValue & 0xFFFFF800) ^ ((yearValue << 11) + (monthValue << 6) + (dayValue)); } @Override public String toString() { int yearValue = year; int monthValue = month; int dayValue = day; int absYear = Math.abs(yearValue); StringBuilder buf = new StringBuilder(10); if (absYear < 1000) { if (yearValue < 0) { buf.append(yearValue - 10000).deleteCharAt(1); } else { buf.append(yearValue + 10000).deleteCharAt(0); } } else { if (yearValue > 9999) { buf.append('+'); } buf.append(yearValue); } return buf.append(monthValue < 10 ? "-0" : "-") .append(monthValue) .append(dayValue < 10 ? "-0" : "-") .append(dayValue) .toString(); } }