From ea35a134fe9c9a2b39b4c168358185c178fcdb26 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Wed, 12 Aug 2026 12:06:25 -0400 Subject: [PATCH] Show the fiscal week beside the site name The classic ASP site prints a week number under the site title, and people quote it in conversation and on paperwork. Anyone with both sites open needs the two to agree, so this is a port of the old arithmetic rather than a fresh interpretation of what a fiscal week is: ISO 8601, week 1 contains 4 January, and the week's Thursday decides which year it belongs to. That rule is what makes late December and early January land in the right year, which is exactly where a naive day-of-year count goes wrong, so it is what the tests cover. Worth recording: a true GE fiscal calendar need not follow ISO weeks. Nobody has asked for a different rule, and inventing one here would silently disagree with the site people compare against. Computed in local time on purpose. The number people quote is the one on the wall where they stand, and a UTC week rolls over hours early in the evening at a US site. The sidebar re-checks every half hour; the shop-floor board picks it up with the clock it already ticks. --- frontend/src/utils/fiscalWeek.js | 48 ++++++++++++++++++++++ frontend/src/utils/fiscalWeek.spec.js | 49 +++++++++++++++++++++++ frontend/src/views/AppLayout.vue | 42 ++++++++++++++++++- frontend/src/views/ShopfloorDashboard.vue | 15 +++++++ 4 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 frontend/src/utils/fiscalWeek.js create mode 100644 frontend/src/utils/fiscalWeek.spec.js diff --git a/frontend/src/utils/fiscalWeek.js b/frontend/src/utils/fiscalWeek.js new file mode 100644 index 0000000..883fed3 --- /dev/null +++ b/frontend/src/utils/fiscalWeek.js @@ -0,0 +1,48 @@ +/** + * Fiscal week, as the classic ASP site computed it. + * + * That site called it "Fiscal Week" and calculated an ISO 8601 week number + * (includes/leftsidebar.asp): week 1 is the week containing 4 January, weeks + * start on Monday, and the year is decided by that week's Thursday. This is a + * faithful port - the label and the arithmetic both come from there. + * + * Worth knowing if the numbers are ever questioned: a true GE fiscal calendar + * need not line up with ISO weeks. Nobody has asked for a different rule, and + * changing it here would silently disagree with the classic site people still + * compare against, so it stays ISO until someone says otherwise. + */ + +/** + * ISO 8601 week number for a date (1-53). + * + * Works in local time deliberately: the number people quote is the one on the + * wall where they are, and a UTC-based week rolls over hours early in the + * evening at a US site. + */ +export function fiscalWeek(date = new Date()) { + const day = new Date(date.getFullYear(), date.getMonth(), date.getDate()) + + // The Thursday of this week decides which year the week belongs to, which is + // what makes late December and early January land in the right year. + const dayOfWeek = (day.getDay() + 6) % 7 // 0 = Monday + const thursday = new Date(day) + thursday.setDate(day.getDate() + 3 - dayOfWeek) + + // Week 1 is the week containing 4 January; find its Monday. + const jan4 = new Date(thursday.getFullYear(), 0, 4) + const jan4DayOfWeek = (jan4.getDay() + 6) % 7 + const week1Monday = new Date(jan4) + week1Monday.setDate(jan4.getDate() - jan4DayOfWeek) + + const days = Math.round((thursday - week1Monday) / 86400000) + return Math.floor(days / 7) + 1 +} + +/** The year the week belongs to, which is not always the calendar year. */ +export function fiscalWeekYear(date = new Date()) { + const day = new Date(date.getFullYear(), date.getMonth(), date.getDate()) + const dayOfWeek = (day.getDay() + 6) % 7 + const thursday = new Date(day) + thursday.setDate(day.getDate() + 3 - dayOfWeek) + return thursday.getFullYear() +} diff --git a/frontend/src/utils/fiscalWeek.spec.js b/frontend/src/utils/fiscalWeek.spec.js new file mode 100644 index 0000000..a65888a --- /dev/null +++ b/frontend/src/utils/fiscalWeek.spec.js @@ -0,0 +1,49 @@ +import { describe, it, expect } from 'vitest' +import { fiscalWeek, fiscalWeekYear } from './fiscalWeek' + +/** + * The number has to match what the classic ASP site shows, because people will + * have both open. The awkward cases are all at the year boundary, which is + * exactly where a naive "day of year / 7" gets it wrong. + */ +describe('fiscalWeek', () => { + it('counts the week containing 4 January as week 1', () => { + expect(fiscalWeek(new Date(2026, 0, 4))).toBe(1) + }) + + it('puts a late-December date in week 1 of the NEXT year', () => { + // 2025-12-29 is a Monday; its Thursday falls in 2026, so it is 2026 week 1. + expect(fiscalWeek(new Date(2025, 11, 29))).toBe(1) + expect(fiscalWeekYear(new Date(2025, 11, 29))).toBe(2026) + }) + + it('puts an early-January date in the LAST week of the previous year', () => { + // 2027-01-01 is a Friday; its Thursday is 2026-12-31, so it is 2026 week 53. + expect(fiscalWeek(new Date(2027, 0, 1))).toBe(53) + expect(fiscalWeekYear(new Date(2027, 0, 1))).toBe(2026) + }) + + it('holds the same number all week, Monday through Sunday', () => { + const monday = new Date(2026, 7, 10) + const week = fiscalWeek(monday) + for (let i = 0; i < 7; i++) { + const day = new Date(2026, 7, 10 + i) + expect(fiscalWeek(day)).toBe(week) + } + }) + + it('increments the following Monday', () => { + expect(fiscalWeek(new Date(2026, 7, 17))).toBe(fiscalWeek(new Date(2026, 7, 10)) + 1) + }) + + it('gives a 53-week year its 53rd week', () => { + // 2026 starts on a Thursday, so it runs to 53 weeks. + expect(fiscalWeek(new Date(2026, 11, 31))).toBe(53) + }) + + it('is not fooled by a time of day', () => { + const morning = new Date(2026, 7, 12, 6, 0, 0) + const nearMidnight = new Date(2026, 7, 12, 23, 59, 0) + expect(fiscalWeek(morning)).toBe(fiscalWeek(nearMidnight)) + }) +}) diff --git a/frontend/src/views/AppLayout.vue b/frontend/src/views/AppLayout.vue index bc8146d..19b4a8c 100644 --- a/frontend/src/views/AppLayout.vue +++ b/frontend/src/views/AppLayout.vue @@ -4,6 +4,11 @@