diff --git a/docs/en-us/guides/configuration/global.md b/docs/en-us/guides/configuration/global.md index eb09371a..2de7bbf7 100644 --- a/docs/en-us/guides/configuration/global.md +++ b/docs/en-us/guides/configuration/global.md @@ -14,15 +14,12 @@ import { GANTT_GLOBAL_CONFIG } from 'ngx-gantt'; { provide: GANTT_GLOBAL_CONFIG, useValue: { + locale: 'zh-hans', dateFormat: { - ... - }, - linkOptions: { - ... - }, - styleOptions: { - ... - }, + timeZone: 'Asia/Shanghai', + weekStartsOn: 1 + } + ... } }, ... @@ -41,6 +38,7 @@ export class AppModule { export interface GanttGlobalConfig { locale: GanttI18nLocale; // i18n locale zh-hans, zh-hant ,en-us, de-de, ja-jp, ru-ru dateOptions: { + timeZone: string, // set custom time zone, default is system's time zone weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 // set the week start value, the default is 1 }; linkOptions: { diff --git a/docs/en-us/guides/configuration/i18n.md b/docs/en-us/guides/configuration/i18n.md index 55585656..349f2c1c 100644 --- a/docs/en-us/guides/configuration/i18n.md +++ b/docs/en-us/guides/configuration/i18n.md @@ -81,3 +81,31 @@ export class AppModule { } ``` + +### Configuring TimeZone + +`ngx-gantt` defaults to using the system timezone, but users can set a custom timezone through the global configuration `GANTT_GLOBAL_CONFIG` by specifying dateOptions.timeZone. + +```javascript +import { GANTT_GLOBAL_CONFIG } from 'ngx-gantt'; + +@NgModule({ + ... + providers: [ + { + provide: GANTT_GLOBAL_CONFIG, + useValue: { + dateOptions: { + timeZone: 'Asia/Shanghai' + } + } + }, + ... + ] + ... +}) +export class AppModule { + +} + +``` diff --git a/docs/zh-cn/guides/configuration/global.md b/docs/zh-cn/guides/configuration/global.md index 34cff747..63615b0e 100644 --- a/docs/zh-cn/guides/configuration/global.md +++ b/docs/zh-cn/guides/configuration/global.md @@ -15,15 +15,12 @@ import { GANTT_GLOBAL_CONFIG } from 'ngx-gantt'; { provide: GANTT_GLOBAL_CONFIG, useValue: { + locale: 'zh-hans', dateFormat: { - ... - }, - linkOptions: { - ... - }, - styleOptions: { - ... - }, + timeZone: 'Asia/Shanghai', + weekStartsOn: 1 + } + ... } }, ... @@ -36,12 +33,13 @@ export class AppModule { ``` -`GANTT_GLOBAL_CONFIG` 格式如下: +`GANTT_GLOBAL_CONFIG` 参数说明: ```javascript export interface GanttGlobalConfig { locale: GanttI18nLocale; // 默认 locale 可选语言:zh-hans, zh-hant ,en-us, de-de, ja-jp, ru-ru dateOptions: { + timeZone: string, // 设置自定义时区,默认为系统默认时区 weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 // 设置 week 起始值,默认为 1 }; linkOptions: { diff --git a/docs/zh-cn/guides/configuration/i18n.md b/docs/zh-cn/guides/configuration/i18n.md index 31dfc13b..e30e3380 100644 --- a/docs/zh-cn/guides/configuration/i18n.md +++ b/docs/zh-cn/guides/configuration/i18n.md @@ -81,4 +81,33 @@ export class AppModule { } +``` + +### 时区 + +`ngx-gantt` 默认使用系统时区,使用者可通过全局配置 `GANTT_GLOBAL_CONFIG` 中的 `dateOptions.timeZone` 来设置自定义时区 + +```javascript +import { GANTT_GLOBAL_CONFIG } from 'ngx-gantt'; + +@NgModule({ + ... + providers: [ + { + provide: GANTT_GLOBAL_CONFIG, + useValue: { + dateOptions: { + timeZone: 'Asia/Shanghai' + } + } + }, + ... + ] + ... +}) +export class AppModule { + +} + + ``` diff --git a/example/src/app/app.module.ts b/example/src/app/app.module.ts index b252a24b..e298a937 100644 --- a/example/src/app/app.module.ts +++ b/example/src/app/app.module.ts @@ -8,7 +8,7 @@ import { ThyNotifyModule } from 'ngx-tethys/notify'; import { ThyDatePickerModule } from 'ngx-tethys/date-picker'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { GANTT_GLOBAL_CONFIG, GANTT_I18N_LOCALE_TOKEN, GanttViewType, NgxGanttModule } from 'ngx-gantt'; +import { GANTT_GLOBAL_CONFIG, NgxGanttModule } from 'ngx-gantt'; import { AppComponent } from './app.component'; import { AppGanttExampleComponent } from './gantt/gantt.component'; import { AppRoutingModule } from './app-routing.module'; @@ -25,7 +25,6 @@ import { AppExampleComponentsComponent } from './components/components.component import { AppGanttGroupsExampleComponent } from './gantt-groups/gantt-groups.component'; import { AppGanttCustomViewExampleComponent } from './gantt-custom-view/gantt.component'; import { AppGanttVirtualScrollExampleComponent } from './gantt-virtual-scroll/gantt.component'; -import { ko } from 'date-fns/locale'; @NgModule({ declarations: [ @@ -56,7 +55,17 @@ import { ko } from 'date-fns/locale'; ThyDatePickerModule, ...EXAMPLE_MODULES ], - providers: [...DOCGENI_SITE_PROVIDERS], + providers: [ + ...DOCGENI_SITE_PROVIDERS, + { + provide: GANTT_GLOBAL_CONFIG, + useValue: { + dateOptions: { + timeZone: 'America/New_York' + } + } + } + ], bootstrap: [AppComponent] }) export class AppModule { diff --git a/example/src/app/gantt/gantt.component.html b/example/src/app/gantt/gantt.component.html index 0735720f..04afc0ce 100644 --- a/example/src/app/gantt/gantt.component.html +++ b/example/src/app/gantt/gantt.component.html @@ -60,12 +60,12 @@ - {{ item.start * 1000 | date : 'yyyy-MM-dd HH:mm' }} + {{ item.start * 1000 | dateFormat : 'yyyy-MM-dd HH:mm' }} - {{ item.end * 1000 | date : 'yyyy-MM-dd HH:mm' }} + {{ item.end * 1000 | dateFormat : 'yyyy-MM-dd HH:mm' }} diff --git a/package-lock.json b/package-lock.json index b6a15ac1..09e95e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,8 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "date-fns": "^2.14.0", + "@date-fns/tz": "^1.2.0", + "date-fns": "^4.1.0", "html2canvas": "1.0.0-rc.7", "rxjs": "^7.5.5", "tslib": "^2.3.0", @@ -4649,6 +4650,12 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", + "license": "MIT" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", @@ -11144,6 +11151,23 @@ "wrap-ansi": "^5.1.0" } }, + "node_modules/concurrently/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/concurrently/node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -13279,15 +13303,22 @@ } }, "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "engines": { - "node": ">=0.11" - }, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" } }, "node_modules/date-format": { @@ -29451,6 +29482,11 @@ } } }, + "@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==" + }, "@discoveryjs/json-ext": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", @@ -34201,6 +34237,15 @@ "wrap-ansi": "^5.1.0" } }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -35888,9 +35933,15 @@ } }, "date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==" + }, + "date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "requires": {} }, "date-format": { "version": "4.0.14", diff --git a/package.json b/package.json index 0456d7a1..685896d5 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "date-fns": "^2.14.0", + "@date-fns/tz": "^1.2.0", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", "html2canvas": "1.0.0-rc.7", "rxjs": "^7.5.5", "tslib": "^2.3.0", diff --git a/packages/gantt/src/gantt-upper.ts b/packages/gantt/src/gantt-upper.ts index bc82141d..2338b1fb 100644 --- a/packages/gantt/src/gantt-upper.ts +++ b/packages/gantt/src/gantt-upper.ts @@ -196,7 +196,7 @@ export abstract class GanttUpper implements OnChanges, OnInit, OnDestroy { this.styles = Object.assign({}, this.configService.config.styleOptions, this.styles); this.viewOptions.dateFormat = Object.assign({}, this.configService.config.dateFormat, this.viewOptions.dateFormat); this.viewOptions.styleOptions = Object.assign({}, this.configService.config.styleOptions, this.viewOptions.styleOptions); - this.viewOptions.dateDisplayFormats = this.configService.getViewsLocale()[this.viewType].dateFormats; + this.viewOptions.dateDisplayFormats = this.configService.getViewsLocale()[this.viewType]?.dateFormats; this.view = createViewFactory(this.viewType, viewDate.start, viewDate.end, this.viewOptions); } diff --git a/packages/gantt/src/gantt.config.ts b/packages/gantt/src/gantt.config.ts index 61f47358..6482acfc 100644 --- a/packages/gantt/src/gantt.config.ts +++ b/packages/gantt/src/gantt.config.ts @@ -23,6 +23,7 @@ export interface GanttDateOptions { * http://gantt.ngnice.com/guides/configuration/i18n */ locale?: Locale; + timeZone?: string; weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; } diff --git a/packages/gantt/src/gantt.module.ts b/packages/gantt/src/gantt.module.ts index 3d46e953..8609d743 100644 --- a/packages/gantt/src/gantt.module.ts +++ b/packages/gantt/src/gantt.module.ts @@ -18,12 +18,13 @@ import { GanttTableHeaderComponent } from './components/table/header/gantt-table import { NgxGanttToolbarComponent } from './components/toolbar/toolbar.component'; import { NgxGanttComponent } from './gantt.component'; import { GANTT_GLOBAL_CONFIG, GanttConfigService, GanttGlobalConfig, defaultConfig } from './gantt.config'; -import { IsGanttBarItemPipe, IsGanttCustomItemPipe, IsGanttRangeItemPipe } from './gantt.pipe'; +import { GanttDateFormatPipe, IsGanttBarItemPipe, IsGanttCustomItemPipe, IsGanttRangeItemPipe } from './gantt.pipe'; import { NgxGanttRootComponent } from './root.component'; import { NgxGanttTableColumnComponent } from './table/gantt-column.component'; import { NgxGanttTableComponent } from './table/gantt-table.component'; import { GanttScrollbarComponent } from './components/scrollbar/scrollbar.component'; import { i18nLocaleProvides } from './i18n'; +import { setDefaultTimeZone } from 'ngx-gantt'; @NgModule({ imports: [ @@ -50,7 +51,8 @@ import { i18nLocaleProvides } from './i18n'; GanttScrollbarComponent, IsGanttRangeItemPipe, IsGanttBarItemPipe, - IsGanttCustomItemPipe + IsGanttCustomItemPipe, + GanttDateFormatPipe ], exports: [ NgxGanttComponent, @@ -64,7 +66,8 @@ import { i18nLocaleProvides } from './i18n'; GanttCalendarHeaderComponent, GanttCalendarGridComponent, GanttDragBackdropComponent, - GanttScrollbarComponent + GanttScrollbarComponent, + GanttDateFormatPipe ], providers: [ CdkVirtualScrollViewport, @@ -78,9 +81,14 @@ import { i18nLocaleProvides } from './i18n'; export class NgxGanttModule { constructor() { const configService = inject(GanttConfigService); + setDefaultOptions({ locale: configService.getDateLocal(), weekStartsOn: configService.config?.dateOptions?.weekStartsOn }); + + if (configService.config.dateOptions?.timeZone) { + setDefaultTimeZone(configService.config.dateOptions.timeZone); + } } } diff --git a/packages/gantt/src/gantt.pipe.ts b/packages/gantt/src/gantt.pipe.ts index ba84a69a..7608d9e6 100644 --- a/packages/gantt/src/gantt.pipe.ts +++ b/packages/gantt/src/gantt.pipe.ts @@ -1,5 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core'; import { GanttItemType } from './class'; +import { GanttDate } from './utils/date'; @Pipe({ name: 'isGanttRangeItem', @@ -30,3 +31,13 @@ export class IsGanttCustomItemPipe implements PipeTransform { return value === GanttItemType.custom; } } + +@Pipe({ + name: 'dateFormat', + standalone: true +}) +export class GanttDateFormatPipe implements PipeTransform { + transform(value: number | string, format: string) { + return new GanttDate(value).format(format); + } +} diff --git a/packages/gantt/src/utils/date.ts b/packages/gantt/src/utils/date.ts index 0f5075da..d4e76ce8 100644 --- a/packages/gantt/src/utils/date.ts +++ b/packages/gantt/src/utils/date.ts @@ -29,9 +29,13 @@ import { startOfMinute, startOfHour, endOfHour, - endOfMinute + endOfMinute, + Locale, + FirstWeekContainsDate } from 'date-fns'; +import { TZDate } from '@date-fns/tz'; + export { Locale, addDays, @@ -76,6 +80,12 @@ export { export type GanttDateUtil = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; +let timeZone: string; + +export function setDefaultTimeZone(zone: string) { + timeZone = zone ?? undefined; +} + export class GanttDate { value: Date; @@ -85,9 +95,9 @@ export class GanttDate { this.value = date; } else if (typeof date === 'string' || typeof date === 'number') { if (date.toString().length < 13) { - this.value = fromUnixTime(+date); + this.value = new TZDate(fromUnixTime(+date), timeZone); } else { - this.value = new Date(date); + this.value = new TZDate(date as any, timeZone); } } else { throw new Error( @@ -96,7 +106,7 @@ export class GanttDate { ); } } else { - this.value = new Date(); + this.value = new TZDate(new Date(), timeZone); } } @@ -280,7 +290,7 @@ export class GanttDate { options?: { locale?: Locale; weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; - firstWeekContainsDate?: number; + firstWeekContainsDate?: FirstWeekContainsDate; useAdditionalWeekYearTokens?: boolean; useAdditionalDayOfYearTokens?: boolean; }