Skip to content

Commit dc273dd

Browse files
committed
Merge branch 'feat/timezone-converter' into chore/all-my-stuffs
# Conflicts: # components.d.ts # package.json # pnpm-lock.yaml # src/tools/index.ts
2 parents fd44be4 + 616e5c9 commit dc273dd

File tree

6 files changed

+167
-3
lines changed

6 files changed

+167
-3
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"countries-db": "^1.2.0",
107107
"composerize": "^1.6.12",
108108
"countries-and-timezones": "^3.7.2",
109+
"countries-and-timezones": "^3.6.0",
109110
"country-code-lookup": "^0.1.0",
110111
"crc": "^4.3.2",
111112
"cron-validator": "^1.3.1",
@@ -135,6 +136,7 @@
135136
"fuse.js": "^6.6.2",
136137
"hash-wasm": "^4.11.0",
137138
"generate-schema": "^2.6.0",
139+
"get-timezone-offset": "^1.0.5",
138140
"highlight.js": "^11.7.0",
139141
"iarna-toml-esm": "^3.0.5",
140142
"ibantools": "^4.3.3",

pnpm-lock.yaml

Lines changed: 10 additions & 3 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
@@ -79,6 +79,7 @@ import { tool as imageToCss } from './image-to-css';
7979
import { tool as jsonToGo } from './json-to-go';
8080
import { tool as jsonToSchema } from './json-to-schema';
8181
import { tool as qrCodeDecoder } from './qr-code-decoder';
82+
import { tool as timezoneConverter } from './timezone-converter';
8283
import { tool as markdownToHtml } from './markdown-to-html';
8384
import { tool as nginxFormatter } from './nginx-formatter';
8485
import { tool as potrace } from './potrace';
@@ -211,6 +212,7 @@ export const toolsByCategory: ToolCategory[] = [
211212
name: 'Converter',
212213
components: [
213214
dateTimeConverter,
215+
timezoneConverter,
214216
baseConverter,
215217
romanNumeralConverter,
216218
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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 use24HourTimeFormat = useStorage<boolean>('timezone-conv:24h', true);
20+
const format = computed(() => use24HourTimeFormat.value ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd hh:mm:ss a');
21+
const timePickerProps = computed(() => use24HourTimeFormat.value ? ({ use12Hours: false }) : ({ use12Hours: true }));
22+
23+
const now = Date.now();
24+
const currentDatetimeRange = ref<[number, number]>([now, now]);
25+
const currentTimezoneOffset = computed(() => {
26+
return convertMinsToHrsMins(-getTimezoneOffset(currentTimezone.value, new Date(currentDatetimeRange.value[0])));
27+
});
28+
function convertToTimezone(tz: string, timestamp: number) {
29+
return new Date(
30+
timestamp
31+
+ getTimezoneOffset(currentTimezone.value, new Date()) * 60 * 1000
32+
- getTimezoneOffset(browserTimezone, new Date()) * 60 * 1000,
33+
).toLocaleString(undefined,
34+
{ timeZone: tz, timeZoneName: undefined, hour12: !use24HourTimeFormat.value });
35+
}
36+
37+
const tzToCountriesInput = ref(browserTimezone);
38+
const tzToCountriesOutput = computed(() => ctz.getCountriesForTimezone(tzToCountriesInput.value));
39+
40+
const allCountries = Object.values(ctz.getAllCountries()).map(c => ({
41+
value: c.id,
42+
label: `${c.name} (${c.id})`,
43+
}));
44+
const countryToTimezonesInput = ref('FR');
45+
const countryToTimezonesOutput = computed(() => ctz.getTimezonesForCountry(countryToTimezonesInput.value));
46+
</script>
47+
48+
<template>
49+
<div>
50+
<c-card title="Timezones Date-Time Converter" mb-2>
51+
<c-select
52+
v-model:value="currentTimezone"
53+
label="Timezone"
54+
label-position="left"
55+
searchable
56+
:options="allTimezones"
57+
mb-2
58+
/>
59+
<n-date-picker
60+
:key="format"
61+
v-model:value="currentDatetimeRange"
62+
type="datetimerange"
63+
:format="format"
64+
:time-picker-props="timePickerProps"
65+
mb-2
66+
/>
67+
68+
<n-space justify="space-evenly">
69+
<n-form-item label="Current Timezone Offset:" label-placement="left">
70+
<n-input :value="currentTimezoneOffset" readonly style="width:5em" />
71+
</n-form-item>
72+
<n-form-item label="Use 24 hour time format" label-placement="left">
73+
<n-switch v-model:value="use24HourTimeFormat" />
74+
</n-form-item>
75+
</n-space>
76+
77+
<c-card title="Date-Time in other timezones">
78+
<n-dynamic-input
79+
v-model:value="otherTimezones"
80+
show-sort-button
81+
:on-create="() => ({ name: browserTimezone })"
82+
>
83+
<template #default="{ value }">
84+
<div flex flex-wrap items-center gap-1>
85+
<n-select
86+
v-model:value="value.name"
87+
filterable
88+
placeholder="Please select a timezone"
89+
:options="allTimezones"
90+
w-full
91+
/>
92+
<div w-full flex items-baseline gap-1>
93+
<n-input style="min-width: 49%" readonly :value="convertToTimezone(value.name, currentDatetimeRange[0])" />
94+
<n-input style="min-width: 49%" readonly :value="convertToTimezone(value.name, currentDatetimeRange[1])" />
95+
</div>
96+
</div>
97+
</template>
98+
</n-dynamic-input>
99+
</c-card>
100+
</c-card>
101+
102+
<c-card title="Country to Timezones" mb-2>
103+
<c-select
104+
v-model:value="countryToTimezonesInput"
105+
label="Country"
106+
label-position="left"
107+
searchable
108+
:options="allCountries"
109+
/>
110+
111+
<n-divider />
112+
113+
<ul>
114+
<li v-for="(tz, ix) in countryToTimezonesOutput" :key="ix">
115+
{{ tz.name }} ({{ tz.countries.join(', ') }}): UTC= {{ tz.utcOffsetStr }}, DST= {{ tz.dstOffsetStr }}
116+
</li>
117+
</ul>
118+
</c-card>
119+
120+
<c-card title="Timezones to Countries" mb-2>
121+
<c-select
122+
v-model:value="tzToCountriesInput"
123+
label="Timezone"
124+
label-position="left"
125+
searchable
126+
:options="allTimezones"
127+
/>
128+
129+
<n-divider />
130+
131+
<ul>
132+
<li v-for="(country, ix) in tzToCountriesOutput" :key="ix">
133+
{{ country.name }} ({{ country.id }}): {{ country.timezones.join(', ') }}
134+
</li>
135+
</ul>
136+
</c-card>
137+
</div>
138+
</template>

0 commit comments

Comments
 (0)