Zettelkasten Forum


Help me generate a bunch of links to daily files?

Hey everyone!

Comes the end of the year, and with it, the desire to plan for the new year. Part of my process for the end part of last year involved working often in both yearly and monthly overview files. I find these useful as a calendar, and also as a place to write summaries of days, task lists for weeks, etc. I keep everything indented so it folds up nicely in vim (and TaskPaper, which I use frequently). The monthly looks something like this:

### November
- W44
    - FRI, Nov 01 [[2024-11-01]]
    - SAT, Nov 02 [[2024-11-02]]
    - SUN, Nov 03 [[2024-11-03]]

- W45
    - WEEK:
    - MON, Nov 04 [[2024-11-04]]
    - TUE, Nov 05 [[2024-11-05]]
    - WED, Nov 06 [[2024-11-06]]
    - THU, Nov 07 [[2024-11-07]]
    - FRI, Nov 08 [[2024-11-08]]
    - SAT, Nov 09 [[2024-11-09]]
    - SUN, Nov 10 [[2024-11-10]]

- W46:

etc etc.

So here's my problem: To generate the monthly version of these files, I essentially did it by hand, every month (cause I just started doing this in about October). To generate the year version, I used a python script that I wrote with the help of ChatGPT, and that is one of the reasons I will never again write anything with the help of ChatGPT. It's terrible and buggy (for example, none of the week numbers like up with the ISO week, and also every week is week 48).

I have a version of this problem every once in a while, and I am sure that at some point in the past I was trying to do something like this and found a tool to generate this exact kind of thing. But now, of course, the rising tide of slop is drowning all search engines (another reason I won't work with ChatGPT) so I can't find it.

So, my actual question: Do any of you know of a tool to generate a list of links like this, with correct week numbers and indentations? Alternately, would any of you like to write me a script to do so? I am sure I could figure it out, but learning that much python definitely falls under the category of "spending time building tools that could be better spent using them". It would be extra nice if I could just feed it an arbitrary date range (use cases like "make just a calendar for the next three months" or whatever) but this is definitely in the nice to have range.

Thanks! Hope you all are well!

Edited to add: I humbly suggest this might also make a useful project for someone wanting to learn how to write plugins for The Archive :wink:

Comments

  • Calendar math is hard!
    https://yourcalendricalfallacyis.com/

    So you best use a tool with a rich Calendar API 🤔 Apple thankfully provides a very robust calendar computation API. But that's only available from Swift, Objective-C, and AppleScript.

    With plain JavaScript (which is used by The Archive's plug-in system!) you don't get answers to "which week of the year is this date". But it's only half bad. You can increment dates with +1 to get to the next day, apparently. Make sure to produce dates at 12:00 noon to avoid +/–1 hour of daylight savings time to accidentally affect the day-of-the-year output.

    function allDaysOfTheYear(year) {
      let calendar = [];
      const start = new Date(year, 0, 1, 12, 00); // Jan 1 noon
      const end = new Date(year, 11, 31, 12, 00); // Dec 31 noon
    
      for (let current = new Date(start); current <= end; current.setDate(current.getDate() + 1)) {
        calendar.push(new Date(current)); // Copy the date object to avoid mutation
      }
    
      return calendar;
    }
    

    With that, you have a list of date objects, one for each day of the year. It looks okay in February (leap month) with 2024 producing 366 and 2025 producing 365 elements.

    Now grouping into weeks, without a richer calendar API, is error prone. This will group all daily date objects into 7-day week groups. That uses the worst kind of date math :( May work for your case, though.

    Please don't use this in production, folks.

    function getWeekNumber(date) {
      const jan1 = new Date(date.getFullYear(), 0, 1);
      const millisecondsInDay = 24 * 60 * 60 * 1000;
      const dayOfWeek = jan1.getDay() || 7; // Adjust Sunday to be 7, not 0
      const offsetDate = date - jan1 + (dayOfWeek - 1) * millisecondsInDay;
      return Math.ceil(offsetDate / (7 * millisecondsInDay));
    }
    
    function daysGroupedByWeek(days) {
      const weeks = days.reduce((result, day) => {
        const weekNumber = getWeekNumber(day);
        if (!result[weekNumber]) {
          result[weekNumber] = []; // Create a new week group if it doesn't exist
        }
        result[weekNumber].push(day);
        return result;
      }, {});
      return weeks;
    }
    

    Then finally to group weeks by month, we can rely on safe getMonth() APIs:

    function weeksGroupByMonth(weeksGroupedByWeek) {
      const monthGroups = Object.values(weeksGroupedByWeek).reduce((result, week) => {
        const monthNumber = week[0].getMonth(); // Use the first day of the week to determine the month
        if (!result[monthNumber]) {
          result[monthNumber] = [];
        }
        result[monthNumber].push(week);
        return result;
      }, {});
      return monthGroups;
    }
    

    To format as Markdown, there's no strftime-like formatter in plain JavaScript! So we need to format like cavepeople: with string concatenation.

    You apparently want UPPERCASE WEEK DAYS for some reason anyway, so manual formatting it is.

    function formatAsMarkdown(monthsGroupedByMonth, locale) {
      const formattedWeekday = (day) => day
        .toLocaleDateString(locale, { weekday: 'short' })
        .toUpperCase();  // Remove this if you don't enjoy shouting like @mediapathic does
      const formattedShortdate = (day) => day.toLocaleDateString(
        locale, {
          month: 'short',
          day: '2-digit',
        }
      );
      const formattedDay = (day) => `    - ${formattedWeekday(day)}, ${formattedShortdate(day)} [[${day.toISOString().split('T')[0]}]]`;
      // Produces: {0: "January", ..., 11: "December"}. the year 1987 is irrelevant as months-per-year don't change much :)
      const monthNames = [...Array(12).keys()].map((monthIndex) => new Date(1987, monthIndex, 1).toLocaleString(locale, { month: 'long' }));
    
      return Object.entries(monthsGroupedByMonth)
        .map(([monthIndex, weeks]) => {
          const monthName = monthNames[monthIndex];
    
          const formattedWeeks = weeks
            .map((week, weekIndex) => {
              const weekLabel = `- W${getWeekNumber(week[0])}`; // Use the first day of the week for week number
              const formattedDays = week
                .map(day => formattedDay(day))
                .join('\n');
              return `${weekLabel}\n${formattedDays}`;
            })
            .join('\n');
    
          return `### ${monthName}\n${formattedWeeks}`;
        })
        .join('\n\n');
    }
    

    Usage with US english locale:

    const year = 2025;
    const days = allDaysOfTheYear(year);
    const weeks = daysGroupedByWeek(days);
    const months = weeksGroupedByMonth(weeks);
    const markdown = formatAsMarkdown(months, 'en-US');
    console.log(markdown);
    

    Needs some manual tweaking where months end because we group by week first, then by month, so you get (with German locale to test):

    - W40
        - MO, 29. Sept. [[2025-09-29]]
        - DI, 30. Sept. [[2025-09-30]]
        - MI, 01. Okt. [[2025-10-01]]
        - DO, 02. Okt. [[2025-10-02]]
        - FR, 03. Okt. [[2025-10-03]]
        - SA, 04. Okt. [[2025-10-04]]
        - SO, 05. Okt. [[2025-10-05]]
    
    ### Oktober
    - W41
        - MO, 06. Okt. [[2025-10-06]]
        - DI, 07. Okt. [[2025-10-07]]
        - MI, 08. Okt. [[2025-10-08]]
        - DO, 09. Okt. [[2025-10-09]]
        - FR, 10. Okt. [[2025-10-10]]
        - SA, 11. Okt. [[2025-10-11]]
        - SO, 12. Okt. [[2025-10-12]]
    

    Tested in The Archive's plug-in console :)

    I admit I've used ChatGPT to generate some boilerplate because I'm not fluent in all JS APIs, then rearranged things :)

    Exercise for the reader

    It should be Trivial™️ for the reader to use the existing code to change the order of grouping: introduce daysGroupedByMonth in a similar fashion to existing code, then split the resulting monthly collections into weeks.

    Author at Zettelkasten.de • https://christiantietze.de/

Sign In or Register to comment.