Skip to content

Commit

Permalink
feat(new tool): Week Numbers Converter
Browse files Browse the repository at this point in the history
  • Loading branch information
sharevb committed Oct 26, 2024
1 parent 1c35ac3 commit 699f327
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 3 deletions.
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>

0 comments on commit 699f327

Please sign in to comment.