Twenty five year rotating clock remix

This sketch creates an interactive 25-year rotating concentric disc clock that displays seconds, minutes, hours, days of the month, months, days of the week, and years. Seven rotating rings spin to show the current time and date, with each number positioned on a circular path that rotates to highlight the active value.

🧪 Try This!

Experiment with the code by making these changes:

  1. Slow down the clock update — Change the refresh rate from 1000ms (1 second) to 5000ms (5 seconds) so the rings spin in larger jumps instead of smoothly.
  2. Make the seconds ring spin outward — Increase the radius of the seconds ring from 320px to 400px so it extends further from the center.
  3. Swap which ring is innermost — Move the day-of-week ring from 90px (innermost) to 200px, and move the years ring from 200px to 90px, so weekdays are now on the outside.
  4. Fix the year function bug — The year() function calls hour() in its recursive setTimeout, which breaks the year ring. Change it to call year() so years continue rotating.
  5. Add active styling visibility
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a stunning circular clock using seven concentric rotating rings—one each for seconds, minutes, hours, days of month, months, days of week, and years. Numbers are positioned in a circle using CSS transforms, and each ring rotates to highlight the current value. The visual effect is hypnotic: as time advances, the rings spin at different speeds, creating a beautiful, gear-like display of temporal information.

The code combines jQuery DOM manipulation to generate and position the numbers, CSS transforms (rotateZ and translateX) to arrange them in circles, and JavaScript recursive timing functions to rotate each ring independently. By studying it, you will learn how to use transform mathematics to create circular layouts, how recursive setTimeout functions can trigger actions at different time intervals, and how to sync multiple animated elements to a real-world clock.

⚙️ How It Works

  1. When the page loads, the draw() function generates list items for each time unit (60 seconds, 60 minutes, 24 hours, 31 days, 12 months, 7 weekdays, 50 years) and appends them to seven separate HTML lists.
  2. The place() function calculates the rotation angle needed for each unit (360/60 for seconds, 360/24 for hours, etc.) and uses CSS transform to position every number on its circular ring at equal intervals using rotateZ() to angle it and translateX() to move it outward.
  3. The clock() function reads the current date and time using JavaScript's Date object, extracting hours, minutes, seconds, year, month, day of month, and day of week.
  4. For each time unit, a dedicated function (sec, min, hour, year, monthOfYear, dayOfMonth, dayOfWeek) is called with the current value. These functions calculate how many degrees to rotate the entire ring to move the current value to the top, apply that rotation via CSS, and add an 'active' class to highlight the current number.
  5. Each timing function uses setTimeout to schedule the next update: seconds update every 1 second, minutes every 60 seconds, hours every 60 minutes, days of week every 7 days, days of month every ~31 days, months every 365 days, and years every 50 years (note: the year function has a bug and calls hour() instead of year() in the recursive call).
  6. The clock() function calls all seven functions once when the page loads to display the current time, then schedules itself to run again every second, continuously keeping the clock synchronized.

🎓 Concepts You'll Learn

CSS transforms (rotateZ, translateX)Circular layout mathematicsjQuery DOM manipulationRecursive timing functionsDate and time APIsActive state management

📝 Code Breakdown

draw()

The draw() function runs once when the page loads (inside $(document).ready()). It populates seven HTML lists with all the numbers and text labels that will be positioned in circles. Each list item gets a data-item attribute for reference and the displayed text. Notice how day names and month names come from pre-defined arrays at the top of the file rather than being generated—this is cleaner than hard-coding them in a loop.

🔬 This loop creates 60 seconds (0-59). What happens if you change the loop condition from i < 60 to i < 12, so only 12 seconds are shown? How does that change what you see on the clock?

  for (i = 0; i < 60; i++) {
    D = i < 10 ? "0" + i : i;
    $("#s").append("<li data-item=" + D + ">" + D + "</li>");
  }
function draw() {
  for (i = 0; i < 60; i++) {
    D = i < 10 ? "0" + i : i;
    $("#s").append("<li data-item=" + D + ">" + D + "</li>");
  }
  for (i = 0; i < 60; i++) {
    D = i < 10 ? "0" + i : i;
    $("#m").append("<li data-item=" + D + ">" + D + "</li>");
  }
  for (i = 0; i < 24; i++) {
    D = i < 10 ? "0" + i : i;
    $("#h").append("<li data-item=" + D + ">" + D + "</li>");
  }
  for (i = 1; i <= 31; i++) {
    D = i < 10 ? "0" + i : i;
    $("#dom").append("<li data-item=" + D + ">" + D + "</li>");
  }
  for (i = 1; i <= 12; i++) {
    D = i < 10 ? "0" + i : i;
    $("#mon").append("<li data-item=" + D + ">" + monthNames[D - 1] + "</li>");
  }
  for (i = 1; i <= 7; i++) {
    D = i;
    $("#dow").append("<li data-item=" + D + ">" + dayNames[D - 1] + "</li>");
  }
  for (i = 0; i < 50; i++) {
    D = i < 10 ? "0" + i : i;
    $("#y").append("<li data-item=" + D + ">20" + D + "</li>");
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

for-loop Seconds Loop for (i = 0; i < 60; i++) {

Creates 60 list items (0-59) for seconds display

for-loop Minutes Loop for (i = 0; i < 60; i++) {

Creates 60 list items (0-59) for minutes display

for-loop Hours Loop for (i = 0; i < 24; i++) {

Creates 24 list items (0-23) for hours display

for-loop Day of Month Loop for (i = 1; i <= 31; i++) {

Creates 31 list items (1-31) for day of month display

for-loop Month Loop for (i = 1; i <= 12; i++) {

Creates 12 list items with month abbreviations (Jan-Dec)

for-loop Day of Week Loop for (i = 1; i <= 7; i++) {

Creates 7 list items with day names (Sun-Sat)

for-loop Year Loop for (i = 0; i < 50; i++) {

Creates 50 list items (2000-2049) for year display

for (i = 0; i < 60; i++) {
Loop runs 60 times (once for each second value 0-59)
D = i < 10 ? "0" + i : i;
Ternary operator: if i is less than 10, prepend a zero (so 3 becomes '03'); otherwise use i as-is. This ensures two-digit formatting.
$("#s").append("<li data-item=" + D + ">" + D + "</li>");
jQuery appends a new list item to the seconds list (#s) containing the formatted number, storing it in the data-item attribute
for (i = 0; i < 24; i++) {
Hours loop runs 24 times for hours 0-23 in 24-hour format
for (i = 1; i <= 31; i++) {
Day of month loop starts at 1 (not 0) and goes to 31 inclusive, since dates are numbered 1-31, not 0-30
$("#mon").append("<li data-item=" + D + ">" + monthNames[D - 1] + "</li>");
Appends month names from the monthNames array using index D-1 (because months are 1-12 but array indices are 0-11)
$("#dow").append("<li data-item=" + D + ">" + dayNames[D - 1] + "</li>");
Appends day names from the dayNames array, using index D-1 to map day numbers to names
for (i = 0; i < 50; i++) {
Year loop runs 50 times to display 50 years (2000-2049), which is a 25-year cycle starting from 2000 and repeating

place()

place() is called once on page load and sets up all the visual positions using pure CSS. The key insight is that rotateZ rotates the item's own angle, then translateX moves it outward—together they create a circle. The smaller the radius (translateX value), the tighter the ring. The angle calculation (degrees per item) is why 60 seconds fit perfectly around a circle without overlapping. This approach is efficient: changing a radius value instantly tightens or spreads a whole ring.

🔬 This places each second number around a circle by rotating it msdeg degrees, then moving it 320 pixels outward. What happens if you change 320 to 150? What if you change msdeg * index to msdeg * index * 2?

  $("#s li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" + msdeg * index + "deg) translateX(" + parseInt(320) + "px)",
    });
  });
function place() {
  hdeg = 360 / 24;
  msdeg = 360 / 60;
  domdeg = 360 / 31;
  mondeg = 360 / 12;
  dowdeg = 360 / 7;
  ydeg = 360 / 50;
  $("#s li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" + msdeg * index + "deg) translateX(" + parseInt(320) + "px)",
    });
  });
  $("#m li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" + msdeg * index + "deg) translateX(" + parseInt(290) + "px)",
    });
  });
  $("#h li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" + hdeg * index + "deg) translateX(" + parseInt(260) + "px)",
    });
  });
  $("#y li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" + ydeg * index + "deg) translateX(" + parseInt(200) + "px)",
    });
  });
  $("#dom li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" +
        domdeg * index +
        "deg) translateX(" +
        parseInt(170) +
        "px)",
    });
  });
  $("#mon li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" +
        mondeg * index +
        "deg) translateX(" +
        parseInt(130) +
        "px)",
    });
  });
  $("#dow li").each(function (index) {
    $(this).css({
      transform:
        "rotateZ(" + dowdeg * index + "deg) translateX(" + parseInt(90) + "px)",
    });
  });
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Degree-Per-Item Calculations hdeg = 360 / 24;

Pre-calculates the angle between each item on each ring: 360 degrees divided by the number of items creates even spacing

for-loop Seconds Positioning $("#s li").each(function (index) {

Iterates through each second item and applies CSS transform to rotate and position it on the outer ring

for-loop Minutes Positioning $("#m li").each(function (index) {

Positions minute items on the ring at a smaller radius (290px vs 320px for seconds)

for-loop Hours Positioning $("#h li").each(function (index) {

Positions hour items using 24 equal angles and a distinct radius

for-loop Date Components Positioning $("#dom li").each(function (index) {

Positions day, month, and year items at increasingly smaller radii, creating concentric rings

hdeg = 360 / 24;
Calculates the angle between consecutive hours: 360 ÷ 24 = 15 degrees per hour
msdeg = 360 / 60;
Calculates the angle between consecutive seconds/minutes: 360 ÷ 60 = 6 degrees each
domdeg = 360 / 31;
Calculates the angle between consecutive days: 360 ÷ 31 ≈ 11.6 degrees per day
mondeg = 360 / 12;
Calculates the angle between consecutive months: 360 ÷ 12 = 30 degrees per month
dowdeg = 360 / 7;
Calculates the angle between consecutive weekdays: 360 ÷ 7 ≈ 51.4 degrees per day
ydeg = 360 / 50;
Calculates the angle between consecutive years: 360 ÷ 50 = 7.2 degrees per year
$("#s li").each(function (index) {
jQuery .each() iterates through every list item in #s (seconds list); the index parameter is the position (0, 1, 2, ...)
"rotateZ(" + msdeg * index + "deg) translateX(" + parseInt(320) + "px)"
Combines two transforms: rotateZ rotates the item around the center (msdeg * index degrees), and translateX moves it 320px outward to the ring's radius
$("#dom li").each(function (index) {
Positions day-of-month items on a ring 170px from center, using domdeg (11.6°) per item
$("#mon li").each(function (index) {
Positions month items on a ring 130px from center, using mondeg (30°) per item
$("#dow li").each(function (index) {
Positions weekday items on the innermost ring at 90px from center

sec(ts, timer)

sec() is the template for all the time-updating functions. It demonstrates the pattern: calculate which item is active (TS), highlight it, calculate the rotation angle needed to move that item to the top, apply the CSS transform, then schedule the next update. The recursive setTimeout approach means each unit runs independently at its own interval. Notice timer as a parameter: when timer is false (called from clock()), it updates once without scheduling. When timer is true (demo mode), it keeps recursing.

function sec(ts, timer) {
  TS = ts % 60;
  if (ts == 0 && timer) min(0, timer);
  deg = (360 / 60) * ts;
  $("#s li").removeClass("active");
  $("#s li").eq(TS).addClass("active");
  $("#s").css({ transform: "rotateZ(-" + deg + "deg)" });
  ts++;
  if (timer)
    setTimeout(function () {
      sec(ts, timer);
    }, TIME * 1000);
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Modulo for Wrapping TS = ts % 60;

Uses modulo operator to keep TS in range 0-59, wrapping around after 60

conditional Cascade to Minutes if (ts == 0 && timer) min(0, timer);

When seconds reach 60 (ts=0 after wrapping), triggers minutes update

calculation Active Highlight $("#s li").eq(TS).addClass("active");

Adds CSS class to highlight the current second number

calculation Ring Rotation $("#s").css({ transform: "rotateZ(-" + deg + "deg)" });

Rotates the entire seconds ring to position the current second at the top

while-loop Recursive Timing setTimeout(function () { sec(ts, timer); }, TIME * 1000);

Schedules the next second update, creating a continuous ticking animation

TS = ts % 60;
Modulo operator (%) returns the remainder: ts % 60 keeps TS in the range 0-59. When ts = 60, TS becomes 0; when ts = 61, TS becomes 1, and so on.
if (ts == 0 && timer) min(0, timer);
If ts has wrapped to 0 AND timer is true (meaning real-time clock mode), call min(0, timer) to advance the minutes ring
deg = (360 / 60) * ts;
Calculates rotation angle: each second is 6 degrees (360÷60), multiplied by ts to get the total rotation needed
$("#s li").removeClass("active");
Removes the 'active' CSS class from all seconds list items, clearing the previous highlight
$("#s li").eq(TS).addClass("active");
jQuery .eq(TS) selects the list item at index TS and adds the 'active' class to highlight it with CSS styling
$("#s").css({ transform: "rotateZ(-" + deg + "deg)" });
Rotates the entire seconds ring backwards (negative) by deg degrees, spinning the ring until the current second aligns with the top
ts++;
Increments ts by 1 for the next call, advancing to the next second
if (timer) setTimeout(function () { sec(ts, timer); }, TIME * 1000);
If timer is true, schedules this function to call itself again after TIME * 1000 milliseconds (TIME seconds). This creates the recursive loop.

min(tm, timer)

min() follows the same pattern as sec(), but updates on a 60-second interval instead of 1-second. It cascades to hour() instead of cascading down like sec() does. This hierarchy ensures all rings stay synchronized: seconds trigger minutes, minutes trigger hours, and so on up the time scale.

function min(tm, timer) {
  TM = tm % 60;
  if (tm == 0 && timer) hour(0, timer);
  deg = (360 / 60) * tm;
  $("#m li").removeClass("active");
  $("#m li").eq(TM).addClass("active");
  $("#m").css({ transform: "rotateZ(-" + deg + "deg)" });
  tm++;
  milsec = 60 * 1000;
  if (timer)
    setTimeout(function () {
      min(tm, timer);
    }, TIME * milsec);
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Minute Modulo TM = tm % 60;

Keeps minute index in range 0-59

conditional Cascade to Hours if (tm == 0 && timer) hour(0, timer);

When minutes wrap to 0, triggers hour update

calculation Minute Rotation Calculation deg = (360 / 60) * tm;

Calculates degrees to rotate minutes ring (6° per minute)

calculation Minute Highlighting $("#m li").eq(TM).addClass("active");

Highlights the current minute with CSS class

calculation Minutes Ring Rotation $("#m").css({ transform: "rotateZ(-" + deg + "deg)" });

Rotates the entire minutes ring to show current minute

while-loop Minute Scheduling setTimeout(function () { min(tm, timer); }, TIME * milsec);

Schedules next minute update (60 seconds * TIME factor)

TM = tm % 60;
Keeps the minute display in range 0-59 using modulo
if (tm == 0 && timer) hour(0, timer);
When tm reaches 60 and wraps to 0, cascades to the hour() function if in timer mode
deg = (360 / 60) * tm;
Each minute is 6 degrees on the circle; tm is the total minutes elapsed
$("#m").css({ transform: "rotateZ(-" + deg + "deg)" });
Rotates the minutes ring in the opposite direction to align the current minute with the top
milsec = 60 * 1000;
Defines 60,000 milliseconds (60 seconds) - the interval between minute updates
setTimeout(function () { min(tm, timer); }, TIME * milsec);
Schedules min() to run again after 60 seconds (or TIME * 60 seconds in demo mode)

hour(th, timer)

hour() operates at an hourly scale. It uses modulo 24 instead of 60 since there are 24 hours in a day. The angle calculation divides the circle into 24 pieces (15° each). Notice there's no cascade to the next unit—dayOfMonth() is not called by hour(). Instead, all time units are triggered by the main clock() function.

function hour(th, timer) {
  TH = th % 24;
  deg = (360 / 24) * th;
  $("#h li").removeClass("active");
  $("#h li").eq(TH).addClass("active");
  $("#h").css({ transform: "rotateZ(-" + deg + "deg)" });
  th++;
  milsec = 60 * 60 * 1000;
  if (timer)
    setTimeout(function () {
      hour(th, timer);
    }, TIME * milsec);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Hour Wrapping TH = th % 24;

Keeps hour in 24-hour format (0-23)

calculation Hour Angle deg = (360 / 24) * th;

Calculates 15° per hour (360÷24)

calculation Hour Highlight $("#h li").eq(TH).addClass("active");

Marks current hour with active class

TH = th % 24;
Uses modulo 24 to keep hours in 0-23 range (wraps after hour 24 back to 0)
deg = (360 / 24) * th;
Divides circle into 24 equal parts, each 15 degrees. Multiplies by th to get total rotation needed.
milsec = 60 * 60 * 1000;
60 minutes × 60 seconds × 1000 milliseconds = 3,600,000 milliseconds (1 hour)

year(ty, timer)

year() displays a 50-year cycle (2000-2049). It updates every 365 days. There is a critical bug in the recursive call: it calls hour(ty, timer) instead of year(ty, timer), meaning the year ring will not continue to rotate in timer mode. This should be fixed to maintain a proper 50-year loop.

function year(ty, timer) {
  TY = ty % 50;
  deg = (360 / 50) * ty;
  $("#y li").removeClass("active");
  $("#y li").eq(TY).addClass("active");
  $("#y").css({ transform: "rotateZ(-" + deg + "deg)" });
  ty++;
  milsec = 365 * 60 * 60 * 1000;
  if (timer)
    setTimeout(function () {
      hour(ty, timer);
    }, TIME * milsec);
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

calculation Year Wrapping (50-year cycle) TY = ty % 50;

Wraps year display after 50 years

calculation Year Rotation deg = (360 / 50) * ty;

Divides circle into 50 parts for years (7.2° each)

TY = ty % 50;
Wraps after 50 years (simulating a 2000-2049 cycle)
deg = (360 / 50) * ty;
Each year is 7.2 degrees (360÷50)
milsec = 365 * 60 * 60 * 1000;
365 days × 60 minutes × 60 seconds × 1000 milliseconds = one year (ignoring leap years)
setTimeout(function () { hour(ty, timer); }, TIME * milsec);
BUG: Calls hour() instead of year(). Should be year(ty, timer) to continue the year cycle.

monthOfYear(tmon, timer)

monthOfYear() rotates the months ring once per year (365 days). It updates at the annual scale. The commented-out code hints that leap year logic was considered but not implemented—365 is hardcoded, so leap years will be slightly off.

function monthOfYear(tmon, timer) {
  TMON = tmon % 12;
  deg = (360 / 12) * tmon;
  $("#mon li").removeClass("active");
  $("#mon li").eq(TMON).addClass("active");
  $("#mon").css({ transform: "rotateZ(-" + deg + "deg)" });
  tmon++;
  milsec = 365 * 24 * 60 * 60 * 1000;
  if (timer)
    setTimeout(function () {
      monthOfYear(tmon, timer);
    }, TIME * milsec);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Month Wrapping TMON = tmon % 12;

Wraps month display after 12 months

calculation Month Rotation deg = (360 / 12) * tmon;

Divides circle into 12 parts for months (30° each)

TMON = tmon % 12;
Modulo 12 wraps months after December back to January (0-11)
deg = (360 / 12) * tmon;
Each month is 30 degrees (360÷12)
milsec = 365 * 24 * 60 * 60 * 1000;
One full year in milliseconds (ignoring leap years, as the comment notes)

dayOfMonth(tdom, timer)

dayOfMonth() shows the day of the month (1-31) and rotates the ring every ~31 days. Notice the hardcoded millisecond value (2,678,400,000) rather than a formula—this corresponds to 31 days. A better approach would compute it: 31 * 24 * 60 * 60 * 1000, but the numeric constant works.

function dayOfMonth(tdom, timer) {
  TDOM = tdom % 31;
  deg = (360 / 31) * tdom;
  $("#dom li").removeClass("active");
  $("#dom li").eq(TDOM).addClass("active");
  $("#dom").css({ transform: "rotateZ(-" + deg + "deg)" });
  tdom++;
  milsec = 2678400000;
  if (timer)
    setTimeout(function () {
      dayOfMonth(tdom, timer);
    }, TIME * milsec);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Day Wrapping TDOM = tdom % 31;

Wraps day display after 31 days

TDOM = tdom % 31;
Wraps days after 31 back to 0 (assumes max 31-day month)
deg = (360 / 31) * tdom;
Divides circle into 31 parts; each day is ~11.6 degrees
milsec = 2678400000;
This is 31 days in milliseconds (31 × 24 × 60 × 60 × 1000 = 2,678,400,000)

dayOfWeek(tdow, timer)

dayOfWeek() displays the current day of the week (Sun-Sat, indices 0-6) and rotates the innermost ring every 7 days. Like dayOfMonth(), it uses a hardcoded millisecond constant (604,800,000 for one week) instead of computing it.

function dayOfWeek(tdow, timer) {
  TDOW = tdow % 7;
  deg = (360 / 7) * tdow;
  $("#dow li").removeClass("active");
  $("#dow li").eq(TDOW).addClass("active");
  $("#dow").css({ transform: "rotateZ(-" + deg + "deg)" });
  tdow++;
  milsec = 604800000;
  if (timer)
    setTimeout(function () {
      dayOfWeek(tdow, timer);
    }, TIME * milsec);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Weekday Wrapping TDOW = tdow % 7;

Wraps after 7 days back to Sunday

TDOW = tdow % 7;
Wraps weekdays after Sunday back to Monday using modulo 7
deg = (360 / 7) * tdow;
Divides circle into 7 parts; each day is ~51.4 degrees
milsec = 604800000;
This is 7 days in milliseconds (7 × 24 × 60 × 60 × 1000 = 604,800,000 milliseconds = 1 week)

isLeapYear(year)

isLeapYear() implements the standard Gregorian calendar leap year rule. It is called in clock() but its result (leapYear variable) is never used in the rest of the code, making this function effectively unused. It was likely prepared for more accurate time calculations but not fully integrated.

function isLeapYear(year) {
  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

conditional Leap Year Logic return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;

Applies the Gregorian leap year rules: divisible by 4 but not 100, OR divisible by 400

return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
A leap year is: (divisible by 4 AND not divisible by 100) OR divisible by 400. Example: 2000 is leap (divisible by 400), 1900 is not (divisible by 100 but not 400), 2004 is leap (divisible by 4, not by 100).

clock()

clock() is the orchestrator. It reads the current time once per second, extracts all time components, and calls the seven rotation functions with timer=0 (meaning they update but don't schedule their own recursive timers). This is different from the TIMER mode commented out in the document-ready handler, which would pass timer=1 to enable demo mode where each function reschedules itself. The structure ensures real-time accuracy: every second, we fetch the actual time and synchronize all rings.

🔬 These seven lines update all the rings to match the current time. Each passes the extracted value (Y, H, M, S, DOFM, MOFY, DOFW) and timer=0. What happens if you comment out the line sec(S, 0)? Will the seconds ring still rotate?

  year(Y, 0);
  hour(H, 0);
  min(M, 0);
  sec(S, 0);
  dayOfMonth(DOFM, 0);
  monthOfYear(MOFY, 0);
  dayOfWeek(DOFW, 0);
function clock() {
  d = new Date();
  H = d.getHours();
  M = d.getMinutes();
  S = d.getSeconds();
  Y = d.getFullYear();
  leapYear = isLeapYear(Y);
  DOFM = d.getDate() - 1;
  MOFY = d.getMonth();
  DOFW = d.getDay();
  year(Y, 0);
  hour(H, 0);
  min(M, 0);
  sec(S, 0);
  dayOfMonth(DOFM, 0);
  monthOfYear(MOFY, 0);
  dayOfWeek(DOFW, 0);
  setTimeout(function () {
    clock();
  }, 1000);
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Date Extraction d = new Date();

Gets current date and time

calculation Time Components H = d.getHours();

Extracts hour, minute, second, year, day, month from Date object

calculation All Ring Updates year(Y, 0);

Calls all seven rotation functions with timer=0 (no recursive scheduling, just one-time display update)

while-loop Continuous Clock Loop setTimeout(function () { clock(); }, 1000);

Reschedules clock() to run again after 1 second, keeping the display synchronized

d = new Date();
Creates a new Date object representing the current moment
H = d.getHours();
Extracts the hour component (0-23) from the Date object
M = d.getMinutes();
Extracts minutes (0-59)
S = d.getSeconds();
Extracts seconds (0-59)
Y = d.getFullYear();
Extracts the full year (e.g., 2024)
DOFM = d.getDate() - 1;
Gets day of month (1-31) and subtracts 1 to get 0-30 for array indexing
MOFY = d.getMonth();
Gets month index (0-11, where 0=January, 11=December)
DOFW = d.getDay();
Gets day of week (0=Sunday, 1=Monday, ..., 6=Saturday)
year(Y, 0);
Calls year() with the current year value and timer=0 (means: update once, don't schedule recursively)
hour(H, 0); min(M, 0); sec(S, 0); dayOfMonth(DOFM, 0); monthOfYear(MOFY, 0); dayOfWeek(DOFW, 0);
Calls all six other functions with timer=0, updating all rings to show the current time and date
setTimeout(function () { clock(); }, 1000);
Schedules clock() to run again in 1000 milliseconds (1 second), creating a continuous loop that keeps the clock synchronized

📦 Key Variables

dayNames array

Stores abbreviated weekday names (Sun-Sat) used to label the day-of-week ring

let dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
monthNames array

Stores abbreviated month names (Jan-Dec) used to label the month ring

let monthNames = ["Jan", "Feb", ..., "Dec"];
d object

Stores the current Date object, used to extract time and date components

d = new Date();
H number

Stores the current hour (0-23)

H = d.getHours();
M number

Stores the current minute (0-59)

M = d.getMinutes();
S number

Stores the current second (0-59)

S = d.getSeconds();
Y number

Stores the current year (e.g., 2024)

Y = d.getFullYear();
DOFM number

Stores day of month minus 1 (0-30, adjusted for 0-based array indexing)

DOFM = d.getDate() - 1;
MOFY number

Stores month of year (0-11, where 0=January)

MOFY = d.getMonth();
DOFW number

Stores day of week (0=Sunday, 6=Saturday)

DOFW = d.getDay();
leapYear boolean

Stores whether the current year is a leap year (calculated but currently unused)

leapYear = isLeapYear(Y);
deg number

Stores the calculated rotation angle in degrees for a time ring

deg = (360 / 60) * ts;
hdeg number

Pre-calculated degrees per hour (360 / 24 = 15)

hdeg = 360 / 24;
msdeg number

Pre-calculated degrees per second or minute (360 / 60 = 6)

msdeg = 360 / 60;
domdeg number

Pre-calculated degrees per day of month (360 / 31)

domdeg = 360 / 31;
mondeg number

Pre-calculated degrees per month (360 / 12 = 30)

mondeg = 360 / 12;
dowdeg number

Pre-calculated degrees per day of week (360 / 7)

dowdeg = 360 / 7;
ydeg number

Pre-calculated degrees per year (360 / 50 = 7.2)

ydeg = 360 / 50;
TIME number

Global multiplier for timer mode demo speed (used in commented-out TIMER section)

TIME = 1;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG year() function, setTimeout callback

The recursive setTimeout calls hour(ty, timer) instead of year(ty, timer), so in timer demo mode, the year ring will stop rotating after one update and the hour ring will restart instead.

💡 Change setTimeout callback to call year(ty, timer) to maintain the year rotation cycle.

PERFORMANCE clock() function

jQuery selectors like $("#s li") and $("#s") are queried every frame (7 times per second across 7 functions × every second), but the DOM structure never changes. Caching these selectors would reduce overhead.

💡 Cache jQuery selectors at the top level: let $secRing = $("#s"); let $secItems = $("#s li"); and reuse them in sec(), min(), etc. functions instead of querying the DOM repeatedly.

BUG dayOfMonth() and monthOfYear() functions

monthOfYear() uses 365 days (hardcoded) to calculate interval, ignoring leap years. The commented-out leap year logic suggests this was intended but never completed.

💡 Use the isLeapYear() function result to adjust milsec: milsec = (isLeapYear(Y) ? 366 : 365) * 24 * 60 * 60 * 1000; or pass Y as a parameter to monthOfYear().

STYLE Variable naming

Variable names are inconsistent: single-letter globals (H, M, S, Y, D) and uppercase abbreviations (TM, TH, TS, DOFM, MOFY, DOFW, TMON, TY, TDOW) are harder to read than descriptive camelCase names.

💡 Rename: H → currentHour, M → currentMinute, S → currentSecond, Y → currentYear, TM → currentMinuteDisplay, TS → currentSecondDisplay, etc. This improves code readability without changing functionality.

FEATURE isLeapYear() function

isLeapYear() is calculated but the leapYear variable is never used. The function is defined but has no impact on the clock's operation.

💡 Either remove the unused function and variable, or integrate it into monthOfYear() and dayOfMonth() to calculate accurate day counts per month/year.

STYLE Global variable pollution

Many variables are declared implicitly (without var/let/const) at function scope, becoming global properties that can be accidentally overwritten. Examples: D, i in loops are global.

💡 Use proper variable declarations: const and let instead of implicit globals. For example: for (let i = 0; i < 60; i++) instead of for (i = 0; i < 60; i++).

🔄 Code Flow

Code flow showing draw, place, sec, min, hour, year, monthofyear, dayofmonth, dayofweek, isleapyear, clock

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> seconds-loop[Seconds Loop] draw --> minutes-loop[Minutes Loop] draw --> hours-loop[Hours Loop] draw --> dom-loop[Day of Month Loop] draw --> month-loop[Month Loop] draw --> dow-loop[Day of Week Loop] draw --> year-loop[Year Loop] seconds-loop --> degree-calculations[Degree-Per-Item Calculations] seconds-loop --> seconds-placement[Seconds Positioning] minutes-loop --> degree-calculations minutes-loop --> minutes-placement[Minutes Positioning] hours-loop --> degree-calculations hours-loop --> hours-placement[Hours Positioning] dom-loop --> date-components-placement[Date Components Positioning] month-loop --> date-components-placement dow-loop --> date-components-placement year-loop --> date-components-placement sec --> modulo-operation[Modulo for Wrapping] sec --> active-state-management[Active Highlight] sec --> ring-rotation[Ring Rotation] sec --> recursive-schedule[Recursive Timing] min --> minute-wrapping[Minute Modulo] min --> cascade-to-hour[Cascade to Hours] min --> minute-rotation-calc[Minute Rotation Calculation] min --> minute-highlight[Minute Highlighting] min --> minute-ring-rotation[Minutes Ring Rotation] min --> minute-timeout[Minute Scheduling] hour --> hour-modulo[Hour Wrapping] hour --> hour-angle[Hour Angle] hour --> hour-highlight[Hour Highlight] year --> year-wrapping[Year Wrapping] year --> year-rotation[Year Rotation] monthofyear --> month-modulo[Month Wrapping] monthofyear --> month-rotation[Month Rotation] dayofmonth --> dom-modulo[Day Wrapping] dayofweek --> dow-modulo[Weekday Wrapping] isleapyear --> leap-logic[Leap Year Logic] clock --> date-extraction[Date Extraction] clock --> time-components[Time Components] clock --> all-ring-updates[All Ring Updates] clock --> continuous-loop[Continuous Clock Loop] click setup href "#fn-setup" click draw href "#fn-draw" click seconds-loop href "#sub-seconds-loop" click minutes-loop href "#sub-minutes-loop" click hours-loop href "#sub-hours-loop" click dom-loop href "#sub-dom-loop" click month-loop href "#sub-month-loop" click dow-loop href "#sub-dow-loop" click year-loop href "#sub-year-loop" click degree-calculations href "#sub-degree-calculations" click seconds-placement href "#sub-seconds-placement" click minutes-placement href "#sub-minutes-placement" click hours-placement href "#sub-hours-placement" click date-components-placement href "#sub-date-components-placement" click modulo-operation href "#sub-modulo-operation" click active-state-management href "#sub-active-state-management" click ring-rotation href "#sub-ring-rotation" click recursive-schedule href "#sub-recursive-schedule" click minute-wrapping href "#sub-minute-wrapping" click cascade-to-hour href "#sub-cascade-to-hour" click minute-rotation-calc href "#sub-minute-rotation-calc" click minute-highlight href "#sub-minute-highlight" click minute-ring-rotation href "#sub-minute-ring-rotation" click minute-timeout href "#sub-minute-timeout" click hour-modulo href "#sub-hour-modulo" click hour-angle href "#sub-hour-angle" click hour-highlight href "#sub-hour-highlight" click year-wrapping href "#sub-year-wrapping" click year-rotation href "#sub-year-rotation" click month-modulo href "#sub-month-modulo" click month-rotation href "#sub-month-rotation" click dom-modulo href "#sub-dom-modulo" click dow-modulo href "#sub-dow-modulo" click leap-logic href "#sub-leap-logic" click date-extraction href "#sub-date-extraction" click time-components href "#sub-time-components" click all-ring-updates href "#sub-all-ring-updates" click continuous-loop href "#sub-continuous-loop"

❓ Frequently Asked Questions

What visual experience does the 'Twenty five year rotating clock remix' sketch provide?

This sketch creates a visually engaging representation of a clock with rotating concentric discs that display hours, minutes, days, months, and years, giving a unique perspective on time.

How can users interact with the rotating clock sketch?

Users can interact with the sketch by observing the dynamic rotation of the discs, which visually represent different time units, but it does not include direct interactive controls.

What creative coding techniques are showcased in this p5.js clock remix?

The sketch demonstrates the use of CSS transformations and animations to create a rotating effect, as well as the manipulation of DOM elements to represent various time units visually.

Preview

Twenty five year rotating clock remix - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Twenty five year rotating clock remix - Code flow showing draw, place, sec, min, hour, year, monthofyear, dayofmonth, dayofweek, isleapyear, clock
Code Flow Diagram