Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Used for a quick validation of days in a month.
- // Note filler at MONTHDAYS[0] because months are 1-based.
- private static final int[] MONTHDAYS =
- { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
- private static final boolean isLeap(final int year) {
- return (year % 4) == 0 && (year % 100 != 0 || year % 400 == 0);
- }
- /**
- * Calculate the day of the week (1=Monday, 7=Sunday) for the supplied
- * (valid) date.
- * @param year The year to calculate (year 1 or more recent)
- * @param month The month (1 through 12 for January through December)
- * @param day The Day-in-month (1 through 28/29/30/31 depending on the month)
- * @return The day of the week (1 represents Monday, 7 represents Sunday).
- * @throws IllegalArgumentException if the input values are not a valid date
- */
- public static final int computeDayOfWeek(final int year, final int month, final int day)
- throws IllegalArgumentException {
- // easy checks for valid dates.
- if (year < 1 || month < 1 || month > 12 || day < 1 || day > MONTHDAYS[month]) {
- throw new IllegalArgumentException(String.format(
- "%04d-%02d-%02d is not a valid date", year, month, day));
- }
- // leap year validation
- if (day == 29 && month == 2 && !isLeap(year)) {
- throw new IllegalArgumentException(
- String.format(
- "%04d-%02d-%02d is not a valid date (Feb 29 but not a leap year)",
- year, month, day));
- }
- // Forumla is here: https://en.wikipedia.org/wiki/Zeller%27s_congruence
- // Using the wikipedia's variable names:
- // h = (q + floor((13*(m+1)/5)) + K + floor(K/4) + floor(J/4) + 5J)
- // translate variable names in to algorithm components.
- // q is the day of month.
- final int q = day;
- // m is the month, but Jan and Feb need to be month 13 and 14
- // respectively
- final int m = month + (month < 3 ? 12 : 0);
- // if the month is jan, or feb, then year is of the previous year.
- final int calcyear = year - (month < 3 ? 1 : 0);
- // K is the year-in-century
- final int K = calcyear % 100;
- // J is the century number
- final int J = calcyear / 100;
- /*
- Algorithm works by following a concept of adding days in to a
- sequence, and performing modular arithmetic. The fundamental concept
- is that if you know one specific date's day-of-week, then all you
- need to do is calculate how many days you are in front of, or behind
- that day.
- If you know the days, you can perform a %7 on that, and get the day
- difference.
- If you choose your algorithm to start on a specific day and call it
- 0, then the difference from the %7 is simply the day. As it happens,
- to make things work well, starting with Saturday as day 0 is right.
- */
- // how many days (possibly %7) are we offset at this point in time?
- int offset = 0;
- /*
- Now, how to calculate the days between. Well, there are 36524 days in
- a century, unless the century is divisible by 4, in which case there is
- an extra day. so, the days between epoch and now is the number of
- centuries * days-in-century + number-of-4-centuries.
- But, because we only need extra days, we can do these things %7. So,
- since 36524%7 is 5, we need to add 5 days for each century since epoch.
- Additionally, we need to add another day for each of those special
- leap years that are divisible by 400
- */
- // 5 days per century plus the number of 400 years too.
- offset += J * 5 + J / 4;
- // now, inside a century, there's leap years....
- // a normal year has 365 days, which is 52 weeks and 1 day.
- // so, each year since the start, is 1 more day of offset. And, each
- // leap year adds another...
- offset += K + K / 4;
- /*
- So, that gives us the right number of offset days to get us to the
- current year.
- Now, if we start our logical year on March 1st, we don't need to
- worry about the odd day at the end of february. Also, the number of
- days in a month, starting from March, is:
- Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec,Jan,Feb
- 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 28/29
- Notice how Aug and Jan are at positions 5 and 10?
- Also, a 30-day month adds 2 days of offset, and a 31-day month adds 3
- days of offset. So, for each 30 day month we add 2 days, and for each
- 31 day month we add 3.
- This formula can be hard-coded as (13 * (m + 1))/5
- */
- offset += (13 * (m + 1)) / 5;
- // Now all we need to do is add the days in our current month, to get
- // the final offset:
- offset += q;
- // Then, the actual day of week is the zero-day + offset % 7
- int h = offset % 7;
- // Now adjust the 0-6 based Saturday-Friday to a 1-7 based Monday-Sunday
- return ((h + 5) % 7) + 1;
- }
- /*
- * method for testing only.
- */
- private static final void check(LocalDateTime then, boolean print) {
- // do an us vs. them comparison
- int us = computeDayOfWeek(
- then.get(ChronoField.YEAR),
- then.get(ChronoField.MONTH_OF_YEAR),
- then.get(ChronoField.DAY_OF_MONTH));
- int them = then.get(ChronoField.DAY_OF_WEEK);
- if (us != them || print) {
- System.out.printf("%14s %14s is %s%n",
- then.toString(), DayOfWeek.of(them), DayOfWeek.of(us));
- }
- if (us != them) {
- throw new IllegalStateException(String.format(
- "Unable to correlate our calculation %d to the system %d",
- us, them));
- }
- }
- public static void main(String[] args) {
- LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.DAYS);
- LocalDateTime past = LocalDateTime.of(1, 1, 1, 0, 0);
- LocalDateTime then = past;
- // test every day since 1 Jan, 0001 through to today
- while (then.isBefore(now)) {
- check(then, false);
- then = then.plusDays(1);
- }
- check(now, true);
- then = LocalDateTime.of(2, 1, 1, 0, 0);
- // recheck, and print the first year's dates.
- while (then.isAfter(past)) {
- then = then.minusDays(1);
- check(then, true);
- }
- LocalDateTime future = now.plusYears(1);
- then = now;
- // check, and print the next year's dates.
- while (then.isBefore(future)) {
- check(then, true);
- then = then.plusDays(1);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement