Skip to content

Commit 699f327

Browse files
committed
feat(new tool): Week Numbers Converter
1 parent 1c35ac3 commit 699f327

File tree

6 files changed

+122
-3
lines changed

6 files changed

+122
-3
lines changed

components.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,19 @@ declare module '@vue/runtime-core' {
130130
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
131131
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
132132
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
133-
NCheckbox: typeof import('naive-ui')['NCheckbox']
134133
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
135134
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
135+
NDatePicker: typeof import('naive-ui')['NDatePicker']
136136
NDivider: typeof import('naive-ui')['NDivider']
137137
NEllipsis: typeof import('naive-ui')['NEllipsis']
138+
NFormItem: typeof import('naive-ui')['NFormItem']
138139
NH1: typeof import('naive-ui')['NH1']
139140
NH3: typeof import('naive-ui')['NH3']
140141
NIcon: typeof import('naive-ui')['NIcon']
142+
NInputNumber: typeof import('naive-ui')['NInputNumber']
141143
NLayout: typeof import('naive-ui')['NLayout']
142144
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
143145
NMenu: typeof import('naive-ui')['NMenu']
144-
NSpace: typeof import('naive-ui')['NSpace']
145-
NTable: typeof import('naive-ui')['NTable']
146146
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
147147
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
148148
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
@@ -185,6 +185,7 @@ declare module '@vue/runtime-core' {
185185
UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default']
186186
UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default']
187187
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
188+
WeekNumberConverter: typeof import('./src/tools/week-number-converter/week-number-converter.vue')['default']
188189
WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default']
189190
XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
190191
XmlToJson: typeof import('./src/tools/xml-to-json/xml-to-json.vue')['default']

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
44
import { tool as emailNormalizer } from './email-normalizer';
5+
import { tool as weekNumberConverter } from './week-number-converter';
56

67
import { tool as asciiTextDrawer } from './ascii-text-drawer';
78

@@ -116,6 +117,7 @@ export const toolsByCategory: ToolCategory[] = [
116117
xmlToJson,
117118
jsonToXml,
118119
markdownToHtml,
120+
weekNumberConverter,
119121
],
120122
},
121123
{
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Calendar } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Week Numbers Converter',
6+
path: '/week-number-converter',
7+
description: 'Compute Week Number in Year/Month vs Date',
8+
keywords: ['week', 'month', 'number', 'converter'],
9+
component: () => import('./week-number-converter.vue'),
10+
icon: Calendar,
11+
createdAt: new Date('2024-08-15'),
12+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { getWeekOfMonth } from 'date-fns';
3+
import { getFirstMondayFromISOWeek, getFirstMondayFromMonthWeek } from './week-number-converter.service';
4+
5+
describe('week-number-converter', () => {
6+
describe('getFirstMondayFromISOWeek', () => {
7+
it('return right monday date from week number', () => {
8+
expect(getFirstMondayFromISOWeek(11, 2022).toDateString()).toBe('Mon Mar 14 2022');
9+
expect(getFirstMondayFromISOWeek(1, 2023).toDateString()).toBe('Mon Jan 02 2023');
10+
expect(getFirstMondayFromISOWeek(53, 2026).toDateString()).toBe('Mon Dec 28 2026');
11+
});
12+
});
13+
describe('getFirstMondayFromMonthWeek', () => {
14+
it('return right date from month week number', () => {
15+
expect(getFirstMondayFromMonthWeek(getWeekOfMonth(new Date('2022-03-14')), 3, 2022).toDateString()).toBe('Mon Mar 14 2022');
16+
expect(getFirstMondayFromMonthWeek(getWeekOfMonth(new Date('2023-01-02')), 1, 2023).toDateString()).toBe('Mon Jan 02 2023');
17+
expect(getFirstMondayFromMonthWeek(getWeekOfMonth(new Date('2026-12-28')), 12, 2026).toDateString()).toBe('Mon Dec 28 2026');
18+
});
19+
});
20+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Returns the first day (Monday) of the specified week
2+
3+
// Year defaults to the current local calendar year
4+
export function getFirstMondayFromISOWeek(weekInYear: number, year = new Date().getFullYear()) {
5+
const d = new Date(year, 0, 4);
6+
d.setDate(d.getDate() - (d.getDay() || 7) + 1 + 7 * (weekInYear - 1));
7+
return d;
8+
}
9+
export function getFirstMondayFromMonthWeek(weekInMonth: number, month = new Date().getMonth() + 1, year = new Date().getFullYear()) {
10+
const d = new Date(year, month - 1, 4);
11+
const day = d.getDay() || 7;
12+
d.setDate(d.getDate() - day + 1);
13+
d.setDate(d.getDate() + 7 * (weekInMonth - 1));
14+
return d;
15+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script setup lang="ts">
2+
import { getWeek, getWeekOfMonth } from 'date-fns';
3+
import { getFirstMondayFromISOWeek, getFirstMondayFromMonthWeek } from './week-number-converter.service';
4+
5+
const now = new Date();
6+
7+
const inputDate = ref(now.getTime());
8+
const outputWeekInMonth = computed(() => getWeekOfMonth(inputDate.value));
9+
const outputWeekInYear = computed(() => getWeek(inputDate.value));
10+
11+
const inputWeekInMonth = ref({
12+
week: getWeekOfMonth(now),
13+
month: now.getMonth() + 1,
14+
year: now.getFullYear(),
15+
});
16+
const outputWeekInMonthMonday = computed(() => getFirstMondayFromMonthWeek(inputWeekInMonth.value.week, inputWeekInMonth.value.month, inputWeekInMonth.value.year));
17+
18+
const inputWeekInYear = ref({
19+
week: getWeek(now),
20+
year: now.getFullYear(),
21+
});
22+
const outputWeekInYearMonday = computed(() => getFirstMondayFromISOWeek(inputWeekInYear.value.week, inputWeekInYear.value.year));
23+
</script>
24+
25+
<template>
26+
<div>
27+
<c-card title="Date to Week numbers" mb-2>
28+
<n-form-item label="Date:" label-placement="left">
29+
<n-date-picker v-model:value="inputDate" type="date" />
30+
</n-form-item>
31+
32+
<n-divider />
33+
34+
<input-copyable readonly label="Week in Year:" label-position="left" label-width="120px" :value="outputWeekInYear" mb-1 />
35+
<input-copyable readonly label="Week in Month:" label-position="left" label-width="120px" :value="outputWeekInMonth" mb-1 />
36+
</c-card>
37+
<c-card title="Year Week Number to Date" mb-2>
38+
<div flex items-baseline gap-2>
39+
<n-form-item label="Week in Year:" label-placement="left" flex-1>
40+
<n-input-number v-model:value="inputWeekInYear.week" :min="1" :max="53" />
41+
</n-form-item>
42+
<n-form-item label="Year:" label-placement="left" flex-1>
43+
<n-input-number v-model:value="inputWeekInYear.year" />
44+
</n-form-item>
45+
</div>
46+
47+
<n-divider />
48+
49+
<input-copyable readonly label="First Monday" label-position="left" :value="outputWeekInYearMonday" />
50+
</c-card>
51+
<c-card title="Month Week Number to Date" mb-2>
52+
<div flex items-baseline gap-2>
53+
<n-form-item label="Week in Month:" label-placement="left" flex-1>
54+
<n-input-number v-model:value="inputWeekInMonth.week" :min="1" :max="5" />
55+
</n-form-item>
56+
<n-form-item label="Month:" label-placement="left" flex-1>
57+
<n-input-number v-model:value="inputWeekInMonth.month" :min="1" :max="12" />
58+
</n-form-item>
59+
<n-form-item label="Year:" label-placement="left" flex-1>
60+
<n-input-number v-model:value="inputWeekInMonth.year" />
61+
</n-form-item>
62+
</div>
63+
64+
<n-divider />
65+
66+
<input-copyable readonly label="First Monday" label-position="left" :value="outputWeekInMonthMonday" />
67+
</c-card>
68+
</div>
69+
</template>

0 commit comments

Comments
 (0)