Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(new tool): Week Numbers Converter #1336

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,19 @@ declare module '@vue/runtime-core' {
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NFormItem: typeof import('naive-ui')['NFormItem']
NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NSpace: typeof import('naive-ui')['NSpace']
NTable: typeof import('naive-ui')['NTable']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
Expand Down Expand Up @@ -185,6 +185,7 @@ declare module '@vue/runtime-core' {
UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default']
UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default']
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
WeekNumberConverter: typeof import('./src/tools/week-number-converter/week-number-converter.vue')['default']
WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default']
XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
XmlToJson: typeof import('./src/tools/xml-to-json/xml-to-json.vue')['default']
Expand Down
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as emailNormalizer } from './email-normalizer';
import { tool as weekNumberConverter } from './week-number-converter';

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

Expand Down Expand Up @@ -116,6 +117,7 @@ export const toolsByCategory: ToolCategory[] = [
xmlToJson,
jsonToXml,
markdownToHtml,
weekNumberConverter,
],
},
{
Expand Down
12 changes: 12 additions & 0 deletions src/tools/week-number-converter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Calendar } from '@vicons/tabler';
import { defineTool } from '../tool';

export const tool = defineTool({
name: 'Week Numbers Converter',
path: '/week-number-converter',
description: 'Compute Week Number in Year/Month vs Date',
keywords: ['week', 'month', 'number', 'converter'],
component: () => import('./week-number-converter.vue'),
icon: Calendar,
createdAt: new Date('2024-08-15'),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import { getWeekOfMonth } from 'date-fns';
import { getFirstMondayFromISOWeek, getFirstMondayFromMonthWeek } from './week-number-converter.service';

describe('week-number-converter', () => {
describe('getFirstMondayFromISOWeek', () => {
it('return right monday date from week number', () => {
expect(getFirstMondayFromISOWeek(11, 2022).toDateString()).toBe('Mon Mar 14 2022');
expect(getFirstMondayFromISOWeek(1, 2023).toDateString()).toBe('Mon Jan 02 2023');
expect(getFirstMondayFromISOWeek(53, 2026).toDateString()).toBe('Mon Dec 28 2026');
});
});
describe('getFirstMondayFromMonthWeek', () => {
it('return right date from month week number', () => {
expect(getFirstMondayFromMonthWeek(getWeekOfMonth(new Date('2022-03-14')), 3, 2022).toDateString()).toBe('Mon Mar 14 2022');
expect(getFirstMondayFromMonthWeek(getWeekOfMonth(new Date('2023-01-02')), 1, 2023).toDateString()).toBe('Mon Jan 02 2023');
expect(getFirstMondayFromMonthWeek(getWeekOfMonth(new Date('2026-12-28')), 12, 2026).toDateString()).toBe('Mon Dec 28 2026');
});
});
});
15 changes: 15 additions & 0 deletions src/tools/week-number-converter/week-number-converter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Returns the first day (Monday) of the specified week

// Year defaults to the current local calendar year
export function getFirstMondayFromISOWeek(weekInYear: number, year = new Date().getFullYear()) {
const d = new Date(year, 0, 4);
d.setDate(d.getDate() - (d.getDay() || 7) + 1 + 7 * (weekInYear - 1));
return d;
}
export function getFirstMondayFromMonthWeek(weekInMonth: number, month = new Date().getMonth() + 1, year = new Date().getFullYear()) {
const d = new Date(year, month - 1, 4);
const day = d.getDay() || 7;
d.setDate(d.getDate() - day + 1);
d.setDate(d.getDate() + 7 * (weekInMonth - 1));
return d;
}
69 changes: 69 additions & 0 deletions src/tools/week-number-converter/week-number-converter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { getWeek, getWeekOfMonth } from 'date-fns';
import { getFirstMondayFromISOWeek, getFirstMondayFromMonthWeek } from './week-number-converter.service';

const now = new Date();

const inputDate = ref(now.getTime());
const outputWeekInMonth = computed(() => getWeekOfMonth(inputDate.value));
const outputWeekInYear = computed(() => getWeek(inputDate.value));

const inputWeekInMonth = ref({
week: getWeekOfMonth(now),
month: now.getMonth() + 1,
year: now.getFullYear(),
});
const outputWeekInMonthMonday = computed(() => getFirstMondayFromMonthWeek(inputWeekInMonth.value.week, inputWeekInMonth.value.month, inputWeekInMonth.value.year));

const inputWeekInYear = ref({
week: getWeek(now),
year: now.getFullYear(),
});
const outputWeekInYearMonday = computed(() => getFirstMondayFromISOWeek(inputWeekInYear.value.week, inputWeekInYear.value.year));
</script>

<template>
<div>
<c-card title="Date to Week numbers" mb-2>
<n-form-item label="Date:" label-placement="left">
<n-date-picker v-model:value="inputDate" type="date" />
</n-form-item>

<n-divider />

<input-copyable readonly label="Week in Year:" label-position="left" label-width="120px" :value="outputWeekInYear" mb-1 />
<input-copyable readonly label="Week in Month:" label-position="left" label-width="120px" :value="outputWeekInMonth" mb-1 />
</c-card>
<c-card title="Year Week Number to Date" mb-2>
<div flex items-baseline gap-2>
<n-form-item label="Week in Year:" label-placement="left" flex-1>
<n-input-number v-model:value="inputWeekInYear.week" :min="1" :max="53" />
</n-form-item>
<n-form-item label="Year:" label-placement="left" flex-1>
<n-input-number v-model:value="inputWeekInYear.year" />
</n-form-item>
</div>

<n-divider />

<input-copyable readonly label="First Monday" label-position="left" :value="outputWeekInYearMonday" />
</c-card>
<c-card title="Month Week Number to Date" mb-2>
<div flex items-baseline gap-2>
<n-form-item label="Week in Month:" label-placement="left" flex-1>
<n-input-number v-model:value="inputWeekInMonth.week" :min="1" :max="5" />
</n-form-item>
<n-form-item label="Month:" label-placement="left" flex-1>
<n-input-number v-model:value="inputWeekInMonth.month" :min="1" :max="12" />
</n-form-item>
<n-form-item label="Year:" label-placement="left" flex-1>
<n-input-number v-model:value="inputWeekInMonth.year" />
</n-form-item>
</div>

<n-divider />

<input-copyable readonly label="First Monday" label-position="left" :value="outputWeekInMonthMonday" />
</c-card>
</div>
</template>
Loading