Skip to content

Commit fcb8ab2

Browse files
committed
feat(new tool): Timezone Converter
Fix CorentinTh#429
1 parent 80e46c9 commit fcb8ab2

File tree

7 files changed

+177
-8
lines changed

7 files changed

+177
-8
lines changed

components.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ declare module '@vue/runtime-core' {
157157
NH3: typeof import('naive-ui')['NH3']
158158
NIcon: typeof import('naive-ui')['NIcon']
159159
NImage: typeof import('naive-ui')['NImage']
160+
NInput: typeof import('naive-ui')['NInput']
160161
NInputGroup: typeof import('naive-ui')['NInputGroup']
161162
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
162163
NInputNumber: typeof import('naive-ui')['NInputNumber']
@@ -165,7 +166,9 @@ declare module '@vue/runtime-core' {
165166
NMenu: typeof import('naive-ui')['NMenu']
166167
NProgress: typeof import('naive-ui')['NProgress']
167168
NScrollbar: typeof import('naive-ui')['NScrollbar']
169+
NSelect: typeof import('naive-ui')['NSelect']
168170
NSlider: typeof import('naive-ui')['NSlider']
171+
NSpace: typeof import('naive-ui')['NSpace']
169172
NStatistic: typeof import('naive-ui')['NStatistic']
170173
NSwitch: typeof import('naive-ui')['NSwitch']
171174
NTable: typeof import('naive-ui')['NTable']
@@ -197,6 +200,7 @@ declare module '@vue/runtime-core' {
197200
TextStatistics: typeof import('./src/tools/text-statistics/text-statistics.vue')['default']
198201
TextToBinary: typeof import('./src/tools/text-to-binary/text-to-binary.vue')['default']
199202
TextToNatoAlphabet: typeof import('./src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue')['default']
203+
TimezoneConverter: typeof import('./src/tools/timezone-converter/timezone-converter.vue')['default']
200204
TokenDisplay: typeof import('./src/tools/otp-code-generator-and-validator/token-display.vue')['default']
201205
'TokenGenerator.tool': typeof import('./src/tools/token-generator/token-generator.tool.vue')['default']
202206
TomlToJson: typeof import('./src/tools/toml-to-json/toml-to-json.vue')['default']

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"change-case": "^4.1.2",
5151
"colord": "^2.9.3",
5252
"composerize-ts": "^0.6.2",
53+
"countries-and-timezones": "^3.6.0",
5354
"country-code-lookup": "^0.1.0",
5455
"cron-validator": "^1.3.1",
5556
"cronstrue": "^2.26.0",
@@ -59,6 +60,7 @@
5960
"emojilib": "^3.0.10",
6061
"figue": "^1.2.0",
6162
"fuse.js": "^6.6.2",
63+
"get-timezone-offset": "^1.0.5",
6264
"highlight.js": "^11.7.0",
6365
"iarna-toml-esm": "^3.0.5",
6466
"ibantools": "^4.3.3",

pnpm-lock.yaml

Lines changed: 25 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
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';
4+
import { tool as timezoneConverter } from './timezone-converter';
45
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
56
import { tool as numeronymGenerator } from './numeronym-generator';
67
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -85,6 +86,7 @@ export const toolsByCategory: ToolCategory[] = [
8586
name: 'Converter',
8687
components: [
8788
dateTimeConverter,
89+
timezoneConverter,
8890
baseConverter,
8991
romanNumeralConverter,
9092
base64StringConverter,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module "get-timezone-offset" {
2+
export default function(timeZoneName: string, date: Date);
3+
}

src/tools/timezone-converter/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CalendarTime } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Timezone Converter',
6+
path: '/timezone-converter',
7+
description: 'Convert Date-Time from a timezone to others and get timezone vs countries infos',
8+
keywords: ['timezone', 'tz', 'date', 'time', 'country', 'converter'],
9+
component: () => import('./timezone-converter.vue'),
10+
icon: CalendarTime,
11+
createdAt: new Date('2024-08-15'),
12+
});
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<script setup lang="ts">
2+
import ctz from 'countries-and-timezones';
3+
import getTimezoneOffset from 'get-timezone-offset';
4+
5+
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
6+
const allTimezones = Object.values(ctz.getAllTimezones()).map(tz => ({
7+
value: tz.name,
8+
label: `${tz.name === browserTimezone ? 'Browser TZ - ' : ''}${tz.name} (${tz.utcOffset === tz.dstOffset ? tz.utcOffsetStr : `${tz.utcOffsetStr}/${tz.dstOffsetStr}`})`,
9+
}));
10+
11+
function convertMinsToHrsMins(minutes: number) {
12+
const h = String(Math.floor(minutes / 60)).padStart(2, '0');
13+
const m = String(minutes % 60).padStart(2, '0');
14+
return `${h}:${m}`;
15+
}
16+
17+
const otherTimezones = useStorage<{ name: string }[]>('timezone-conv:zones', [{ name: 'Etc/GMT' }]);
18+
const currentTimezone = useStorage<string>('timezone-conv:current', browserTimezone);
19+
const now = Date.now();
20+
const currentDatetimeRange = ref<[number, number]>([now, now]);
21+
const currentTimezoneOffset = computed(() => {
22+
return convertMinsToHrsMins(-getTimezoneOffset(currentTimezone.value, new Date(currentDatetimeRange.value[0])));
23+
});
24+
function convertToTimezone(tz: string, timestamp: number) {
25+
return new Date(
26+
timestamp
27+
+ getTimezoneOffset(currentTimezone.value, new Date()) * 60 * 1000
28+
- getTimezoneOffset(browserTimezone, new Date()) * 60 * 1000,
29+
).toLocaleString(undefined,
30+
{ timeZone: tz, timeZoneName: undefined, hour12: false });
31+
}
32+
33+
const tzToCountriesInput = ref(browserTimezone);
34+
const tzToCountriesOutput = computed(() => ctz.getCountriesForTimezone(tzToCountriesInput.value));
35+
36+
const allCountries = Object.values(ctz.getAllCountries()).map(c => ({
37+
value: c.id,
38+
label: `${c.name} (${c.id})`,
39+
}));
40+
const countryToTimezonesInput = ref('FR');
41+
const countryToTimezonesOutput = computed(() => ctz.getTimezonesForCountry(countryToTimezonesInput.value));
42+
</script>
43+
44+
<template>
45+
<div>
46+
<c-card title="Timezones Date-Time Converter" mb-2>
47+
<c-select
48+
v-model:value="currentTimezone"
49+
label="Timezone"
50+
label-position="left"
51+
searchable
52+
:options="allTimezones"
53+
mb-2
54+
/>
55+
<n-date-picker
56+
v-model:value="currentDatetimeRange"
57+
type="datetimerange"
58+
mb-2
59+
/>
60+
61+
<input-copyable
62+
label="Current Timezone Offset (min)"
63+
label-position="left"
64+
:value="currentTimezoneOffset"
65+
mb-2
66+
/>
67+
68+
<c-card title="Date-Time in other timezones">
69+
<n-dynamic-input
70+
v-model:value="otherTimezones"
71+
show-sort-button
72+
:on-create="() => ({ name: browserTimezone })"
73+
>
74+
<template #default="{ value }">
75+
<div flex flex-wrap items-center gap-1>
76+
<n-select
77+
v-model:value="value.name"
78+
filterable
79+
placeholder="Please select a timezone"
80+
:options="allTimezones"
81+
w-full
82+
/>
83+
<div w-full flex items-baseline gap-1>
84+
<n-input style="min-width: 49%" readonly :value="convertToTimezone(value.name, currentDatetimeRange[0])" />
85+
<n-input style="min-width: 49%" readonly :value="convertToTimezone(value.name, currentDatetimeRange[1])" />
86+
</div>
87+
</div>
88+
</template>
89+
</n-dynamic-input>
90+
</c-card>
91+
</c-card>
92+
93+
<c-card title="Country to Timezones" mb-2>
94+
<c-select
95+
label="Country"
96+
label-position="left"
97+
:value="countryToTimezonesInput"
98+
searchable
99+
:options="allCountries"
100+
/>
101+
102+
<n-divider />
103+
104+
<ul>
105+
<li v-for="(tz, ix) in countryToTimezonesOutput" :key="ix">
106+
{{ tz.name }} ({{ tz.countries.join(', ') }}): UTC= {{ tz.utcOffsetStr }}, DST= {{ tz.dstOffsetStr }}
107+
</li>
108+
</ul>
109+
</c-card>
110+
111+
<c-card title="Timezones to Countries" mb-2>
112+
<c-select
113+
label="Timezone"
114+
label-position="left"
115+
:value="tzToCountriesInput"
116+
searchable
117+
:options="allTimezones"
118+
/>
119+
120+
<n-divider />
121+
122+
<ul>
123+
<li v-for="(country, ix) in tzToCountriesOutput" :key="ix">
124+
{{ country.name }} ({{ country.id }}): {{ country.timezones.join(', ') }}
125+
</li>
126+
</ul>
127+
</c-card>
128+
</div>
129+
</template>

0 commit comments

Comments
 (0)