From c7e17b16b7f00a9234387d9b996e9ca5d17b5a58 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Mon, 21 Apr 2025 16:44:23 +0800 Subject: [PATCH 1/9] fix(moment): use timezone from Hexo config instead of maching settings --- lib/hexo/post.ts | 4 +++- lib/models/types/moment.ts | 16 ++++++++++---- lib/plugins/processor/asset.ts | 4 +++- lib/plugins/processor/common.ts | 15 ++++++++++--- lib/plugins/processor/post.ts | 14 ++++++++++--- test/scripts/console/new.ts | 2 +- test/scripts/filters/post_permalink.ts | 29 +++++++++++--------------- test/scripts/models/moment.ts | 10 ++++----- test/scripts/processors/common.ts | 7 ++++--- 9 files changed, 63 insertions(+), 38 deletions(-) diff --git a/lib/hexo/post.ts b/lib/hexo/post.ts index 9418039e4..1ef646004 100644 --- a/lib/hexo/post.ts +++ b/lib/hexo/post.ts @@ -509,7 +509,9 @@ class Post { return readFile(src); }).then(content => { // Create post - Object.assign(data, yfmParse(content)); + Object.assign(data, yfmParse(content, { + defaultTimeZone: config.timezone + })); data.content = data._content; data._content = undefined; diff --git a/lib/models/types/moment.ts b/lib/models/types/moment.ts index e308f96bc..55fa5b9e3 100644 --- a/lib/models/types/moment.ts +++ b/lib/models/types/moment.ts @@ -83,10 +83,18 @@ class SchemaTypeMoment extends warehouse.SchemaType { } function toMoment(value) { - // FIXME: Something is wrong when using a moment instance. I try to get the - // original date object and create a new moment object again. - if (moment.isMoment(value)) return moment((value as any)._d); - return moment(value); + // value passed here is a moment-like instance + // but it's a plain object that methods such as isValid are removed + // moment.isMoment is judged on its property but not constructor + // so the plain object still passes the moment.isMoment check + + // hexo-front-matter now always returns date in UTC + // See https://github.com/hexojs/hexo-front-matter/pull/146 + // We shall specify the timezone UTC here + // Otherwise, `moment()` set the timezone according to the $TZ on the machine + // Which still cause confusion + if (moment.isMoment(value)) return moment((value as any)._d).tz('UTC'); + return moment(value).tz('UTC'); } export = SchemaTypeMoment; diff --git a/lib/plugins/processor/asset.ts b/lib/plugins/processor/asset.ts index b3282b93a..1cd1223bb 100644 --- a/lib/plugins/processor/asset.ts +++ b/lib/plugins/processor/asset.ts @@ -53,7 +53,9 @@ function processPage(ctx: Hexo, file: _File) { file.stat(), file.read() ]).spread((stats: Stats, content: string) => { - const data: PageSchema = yfm(content); + const data: PageSchema = yfm(content, { + defaultTimeZone: config.timezone + }); const output = ctx.render.getOutput(path); data.source = path; diff --git a/lib/plugins/processor/common.ts b/lib/plugins/processor/common.ts index f8eb08b33..91b32483a 100644 --- a/lib/plugins/processor/common.ts +++ b/lib/plugins/processor/common.ts @@ -28,11 +28,21 @@ export {isTmpFile}; export {isHiddenFile}; export {isExcludedFile}; +// This function is used by `asset.ts` and `post.ts` +// To handle dates like `date: Apr 24 2014` in front-matter export function toDate(date?: string | number | Date | moment.Moment): Date | undefined | moment.Moment { if (!date || moment.isMoment(date)) return date as any; if (!(date instanceof Date)) { + // hexo-front-matter now always returns date in UTC + // but `new Date()` uses local timezone by default + // We have to reset offset + // to make the behavior consistent with hexo-front-matter date = new Date(date); + const ms = date.getTime(); + const offset = date.getTimezoneOffset(); + const diff = offset * DURATION_MINUTE; + date = new Date(ms - diff); } if (isNaN(date.getTime())) return; @@ -43,12 +53,11 @@ export function toDate(date?: string | number | Date | moment.Moment): Date | un export function adjustDateForTimezone(date: Date | moment.Moment, timezone: string) { if (moment.isMoment(date)) date = date.toDate(); - const offset = date.getTimezoneOffset(); const ms = date.getTime(); const target = moment.tz.zone(timezone).utcOffset(ms); - const diff = (offset - target) * DURATION_MINUTE; + const diff = target * DURATION_MINUTE; - return new Date(ms - diff); + return new Date(ms + diff); } export {isMatch}; diff --git a/lib/plugins/processor/post.ts b/lib/plugins/processor/post.ts index 0fcc12fb0..5b9f4537a 100644 --- a/lib/plugins/processor/post.ts +++ b/lib/plugins/processor/post.ts @@ -92,7 +92,9 @@ function processPost(ctx: Hexo, file: _File) { file.stat(), file.read() ]).spread((stats: Stats, content: string) => { - const data: PostSchema = yfm(content); + const data: PostSchema = yfm(content, { + defaultTimeZone: config.timezone + }); const info = parseFilename(config.new_post_name, path); const keys = Object.keys(info); @@ -119,15 +121,21 @@ function processPost(ctx: Hexo, file: _File) { } if (data.date) { + // hexo-front-matter now always returns date in UTC + // See https://github.com/hexojs/hexo-front-matter/pull/146 data.date = toDate(data.date) as any; } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)) { - data.date = new Date( + // Date parsed from permalink should also use UTC + // to make the behavior consistent with hexo-front-matter + // It will be corrected by invoking adjustDateForTimezone later + data.date = new Date(Date.UTC( info.year, parseInt(info.month || info.i_month, 10) - 1, parseInt(info.day || info.i_day, 10) - ) as any; + )) as any; } + // Convert date and updated time from UTC to local time (based on timezone in Hexo config) if (data.date) { if (timezone) data.date = adjustDateForTimezone(data.date, timezone) as any; } else { diff --git a/test/scripts/console/new.ts b/test/scripts/console/new.ts index 608d6eed7..13fbb1fb0 100644 --- a/test/scripts/console/new.ts +++ b/test/scripts/console/new.ts @@ -325,7 +325,7 @@ describe('new', () => { const date = moment(now); const path = join(hexo.source_dir, '_posts', 'Hello-World.md'); const body = [ - 'title: \'\'\'Hello\'\' World\'', + 'title: "\'Hello\' World"', 'foo: bar', 'date: ' + date.format('YYYY-MM-DD HH:mm:ss'), 'tags:', diff --git a/test/scripts/filters/post_permalink.ts b/test/scripts/filters/post_permalink.ts index 219dbaa1f..d2c826e48 100644 --- a/test/scripts/filters/post_permalink.ts +++ b/test/scripts/filters/post_permalink.ts @@ -18,7 +18,7 @@ describe('post_permalink', () => { const apost = await Post.insert({ source: 'foo.md', slug: 'foo', - date: moment('2014-01-02') + date: moment.utc('2014-01-02') }); const id = apost._id; await apost.setCategories(['foo', 'bar']); @@ -77,7 +77,7 @@ describe('post_permalink', () => { source: 'sub/2015-05-06-my-new-post.md', slug: '2015-05-06-my-new-post', title: 'My New Post', - date: moment('2015-05-06') + date: moment.utc('2015-05-06') }); postPermalink(post).should.eql('2015/05/06/my-new-post/'); Post.removeById(post._id); @@ -90,7 +90,7 @@ describe('post_permalink', () => { source: 'sub/2015-05-06-my-new-post.md', slug: '2015-05-06-my-new-post', title: 'My New Post', - date: moment('2015-05-06 12:13:14') + date: moment.utc('2015-05-06 12:13:14') }); postPermalink(post).should.eql('2015/05/06/12/13/14/my-new-post/'); Post.removeById(post._id); @@ -100,8 +100,8 @@ describe('post_permalink', () => { hexo.config.permalink = ':timestamp/:slug'; const timestamp = '1736401514'; const dates = [ - moment('2025-01-09 05:45:14Z'), - moment('2025-01-08 22:45:14-07') + moment.utc('2025-01-09 05:45:14Z'), + moment.utc('2025-01-08 22:45:14-07') ]; const posts = await Post.insert( dates.map((date, idx) => { @@ -126,7 +126,7 @@ describe('post_permalink', () => { source: 'sub/2015-05-06-my-new-post.md', slug: '2015-05-06-my-new-post', title: 'My New Post', - date: moment('2015-05-06') + date: moment.utc('2015-05-06') }); postPermalink(post).should.eql('2015/05/06/00/00/00/my-new-post/'); Post.removeById(post._id); @@ -180,14 +180,12 @@ describe('post_permalink', () => { source: 'my-new-post.md', slug: 'hexo/permalink-test', __permalink: 'hexo/permalink-test', - title: 'Permalink Test', - date: moment('2014-01-02') + title: 'Permalink Test' }, { source: 'another-new-post.md', slug: '/hexo-hexo/permalink-test-2', __permalink: '/hexo-hexo/permalink-test-2', - title: 'Permalink Test', - date: moment('2014-01-02') + title: 'Permalink Test' }]); postPermalink(posts[0]).should.eql('/hexo/permalink-test'); @@ -203,7 +201,7 @@ describe('post_permalink', () => { const post = await Post.insert({ source: 'foo.md', slug: 'foo', - date: moment('2014-01-02') + date: moment.utc('2014-01-02') }); postPermalink(post).should.eql('2014/01/02/foo/'); @@ -220,20 +218,17 @@ describe('post_permalink', () => { source: 'my-new-post.md', slug: 'hexo/permalink-test', __permalink: 'hexo/permalink-test', - title: 'Permalink Test', - date: moment('2014-01-02') + title: 'Permalink Test' }, { source: 'another-new-post.md', slug: '/hexo-hexo/permalink-test-2', __permalink: '/hexo-hexo/permalink-test-2/', - title: 'Permalink Test', - date: moment('2014-01-02') + title: 'Permalink Test' }, { source: 'another-another-new-post.md', slug: '/hexo-hexo/permalink-test-3', __permalink: '/hexo-hexo/permalink-test-3.html', - title: 'Permalink Test', - date: moment('2014-01-02') + title: 'Permalink Test' }]); postPermalink(posts[0]).should.eql('/hexo/permalink-test/'); diff --git a/test/scripts/models/moment.ts b/test/scripts/models/moment.ts index e090d38b8..338ede26b 100644 --- a/test/scripts/models/moment.ts +++ b/test/scripts/models/moment.ts @@ -7,10 +7,10 @@ describe('SchemaTypeMoment', () => { const type = new SchemaTypeMoment('test'); it('cast()', () => { - type.cast(1e8).should.eql(moment(1e8)); - type.cast(new Date(2014, 1, 1)).should.eql(moment(new Date(2014, 1, 1))); - type.cast('2014-11-03T07:45:41.237Z').should.eql(moment('2014-11-03T07:45:41.237Z')); - type.cast(moment(1e8)).valueOf().should.eql(1e8); + type.cast(1e8).should.eql(moment.utc(1e8).tz('UTC')); + type.cast(new Date(2014, 1, 1)).should.eql(moment.utc(new Date(2014, 1, 1)).tz('UTC')); + type.cast('2014-11-03T07:45:41.237Z').should.eql(moment.utc('2014-11-03T07:45:41.237Z').tz('UTC')); + type.cast(moment.utc(1e8)).valueOf().should.eql(1e8); }); it('cast() - default', () => { @@ -52,7 +52,7 @@ describe('SchemaTypeMoment', () => { }); it('parse()', () => { - type.parse('2014-11-03T07:45:41.237Z')!.should.eql(moment('2014-11-03T07:45:41.237Z')); + type.parse('2014-11-03T07:45:41.237Z')!.should.eql(moment.utc('2014-11-03T07:45:41.237Z').tz('UTC')); should.not.exist(type.parse()); }); diff --git a/test/scripts/processors/common.ts b/test/scripts/processors/common.ts index 5cc8ee264..016041277 100644 --- a/test/scripts/processors/common.ts +++ b/test/scripts/processors/common.ts @@ -2,6 +2,7 @@ import moment from 'moment'; import { isTmpFile, isHiddenFile, toDate, adjustDateForTimezone, isMatch } from '../../../lib/plugins/processor/common'; import chai from 'chai'; const should = chai.should(); +const DURATION_MINUTE = 1000 * 60; describe('common', () => { it('isTmpFile()', () => { @@ -21,13 +22,13 @@ describe('common', () => { it('toDate()', () => { const m = moment(); const d = new Date(); + const diff = d.getTimezoneOffset() * DURATION_MINUTE; should.not.exist(toDate()); toDate(m)!.should.eql(m); toDate(d)!.should.eql(d); - toDate(1e8)!.should.eql(new Date(1e8)); - toDate('2014-04-25T01:32:21.196Z')!.should.eql(new Date('2014-04-25T01:32:21.196Z')); - toDate('Apr 24 2014')!.should.eql(new Date(2014, 3, 24)); + toDate(1e8)!.should.eql(new Date(1e8 - diff)); + toDate('Apr 24 2014')!.should.eql(new Date(new Date(2014, 3, 24).getTime() - diff)); should.not.exist(toDate('foo')); }); From 63803e158f22014b8f88ee68a2cb7503aedeb4e4 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Tue, 4 Nov 2025 12:45:22 +0800 Subject: [PATCH 2/9] fix(moment): update hexo-front-matter --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a213813ed..6683b2796 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "fast-archy": "^1.0.0", "fast-text-table": "^1.0.1", "hexo-cli": "^4.3.2", - "hexo-front-matter": "^4.2.1", + "hexo-front-matter": "^5.0.0", "hexo-fs": "^5.0.0", "hexo-i18n": "^2.0.0", "hexo-log": "^4.1.0", From 496c833a913bebb773f8bba6469721cfeb46d233 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Tue, 4 Nov 2025 23:16:45 +0800 Subject: [PATCH 3/9] Update lib/plugins/processor/common.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mimi --- lib/plugins/processor/common.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/plugins/processor/common.ts b/lib/plugins/processor/common.ts index 91b32483a..358abdce0 100644 --- a/lib/plugins/processor/common.ts +++ b/lib/plugins/processor/common.ts @@ -39,10 +39,8 @@ export function toDate(date?: string | number | Date | moment.Moment): Date | un // We have to reset offset // to make the behavior consistent with hexo-front-matter date = new Date(date); - const ms = date.getTime(); - const offset = date.getTimezoneOffset(); - const diff = offset * DURATION_MINUTE; - date = new Date(ms - diff); + // Adjust for local timezone offset to ensure UTC consistency + date = new Date(date.getTime() - date.getTimezoneOffset() * DURATION_MINUTE); } if (isNaN(date.getTime())) return; From 15687d4a37a0e87e0d3503967cded05e4abc4c48 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Wed, 5 Nov 2025 00:04:41 +0800 Subject: [PATCH 4/9] fix: eslint --- lib/plugins/processor/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/processor/common.ts b/lib/plugins/processor/common.ts index 358abdce0..6804818fe 100644 --- a/lib/plugins/processor/common.ts +++ b/lib/plugins/processor/common.ts @@ -40,7 +40,7 @@ export function toDate(date?: string | number | Date | moment.Moment): Date | un // to make the behavior consistent with hexo-front-matter date = new Date(date); // Adjust for local timezone offset to ensure UTC consistency - date = new Date(date.getTime() - date.getTimezoneOffset() * DURATION_MINUTE); + date = new Date(date.getTime() - (date.getTimezoneOffset() * DURATION_MINUTE)); } if (isNaN(date.getTime())) return; From 80e436ba3e959cd7ae8a53bd25fbb2722bf26056 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Wed, 5 Nov 2025 11:48:02 +0800 Subject: [PATCH 5/9] feat(validate_config): add timezone check --- lib/hexo/validate_config.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/hexo/validate_config.ts b/lib/hexo/validate_config.ts index 818e8558b..632a51749 100644 --- a/lib/hexo/validate_config.ts +++ b/lib/hexo/validate_config.ts @@ -27,14 +27,15 @@ export = (ctx: Hexo): void => { } if (!config.timezone) { - log.warn('No timezone setting detected! Using LocalTimeZone as the default timezone.'); - log.warn('This behavior will be changed to UTC in the next major version. Please set timezone explicitly (e.g. LocalTimeZone or America/New_York) in _config.yml to avoid this warning.'); + log.warn('No timezone setting detected! Using the default timezone UTC.'); + config.timezone = 'UTC'; } else { const configTimezone = moment.tz.zone(config.timezone); if (!configTimezone) { log.warn( - `Invalid timezone setting detected! "${config.timezone}" is not a valid timezone.` + `Invalid timezone setting detected! "${config.timezone}" is not a valid timezone. Using the default timezone UTC.` ); + config.timezone = 'UTC'; } else { const machineTimezone = moment.tz.guess(); if (configTimezone.name !== machineTimezone) { From 8ded46d10d83507e1baa9fd67c2a3d535eba3011 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Thu, 1 Jan 2026 11:27:21 +0800 Subject: [PATCH 6/9] feat(validate_config): add warning --- lib/hexo/validate_config.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/hexo/validate_config.ts b/lib/hexo/validate_config.ts index 632a51749..b29672be2 100644 --- a/lib/hexo/validate_config.ts +++ b/lib/hexo/validate_config.ts @@ -27,8 +27,11 @@ export = (ctx: Hexo): void => { } if (!config.timezone) { - log.warn('No timezone setting detected! Using the default timezone UTC.'); - config.timezone = 'UTC'; + log.warn('No timezone setting detected! Using LocalTimeZone as the default timezone.'); + log.warn('This behavior will be changed to UTC in the next major version. Please set timezone explicitly (e.g. LocalTimeZone or America/New_York) in _config.yml to avoid this warning.'); + config.timezone = moment.tz.guess(); + } else if (config.timezone.toLowerCase() === 'localtimezone') { + config.timezone = moment.tz.guess(); } else { const configTimezone = moment.tz.zone(config.timezone); if (!configTimezone) { From c9819b03b4c7392b2b9e989c5b7500f648ac19ad Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Fri, 3 Jul 2026 14:44:11 +0800 Subject: [PATCH 7/9] fix: update hexo-front-matter lockfile --- package-lock.json | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8151b7ef5..7d825ba90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hexo", - "version": "8.1.1", + "version": "8.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hexo", - "version": "8.1.1", + "version": "8.1.2", "license": "MIT", "dependencies": { "abbrev": "^3.0.0", @@ -14,7 +14,7 @@ "fast-archy": "^1.0.0", "fast-text-table": "^1.0.1", "hexo-cli": "^4.3.2", - "hexo-front-matter": "^4.2.1", + "hexo-front-matter": "^5.0.0", "hexo-fs": "^5.0.0", "hexo-i18n": "^2.0.0", "hexo-log": "^4.1.0", @@ -4092,15 +4092,28 @@ } }, "node_modules/hexo-front-matter": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hexo-front-matter/-/hexo-front-matter-4.2.1.tgz", - "integrity": "sha512-sJJI0GNmejYiwBvgnGRKn5V3sbODB4dNPr8jyw2Qp0PRHr4Uuyv8iyxw6WfK3+T7yvzYvJOh+tZ7jnwr2BYARA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hexo-front-matter/-/hexo-front-matter-5.0.0.tgz", + "integrity": "sha512-LPjSDyubGKblSJADJI7SOaMGqz98zBNvm639FlmQlqqTHjeg3yXYiw6dL+geIIOeYCR1JF4WLYDVLDCiT6HQIA==", "license": "MIT", "dependencies": { - "js-yaml": "^4.1.0" + "luxon": "^3.6.1", + "yaml": "2.7.1" }, "engines": { - "node": ">=14" + "node": ">=20.19.0" + } + }, + "node_modules/hexo-front-matter/node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" } }, "node_modules/hexo-fs": { @@ -5328,6 +5341,15 @@ "tslib": "^2.0.3" } }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", From 6f6697e874656acd267df0e1c149d490113968b8 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Fri, 3 Jul 2026 14:55:52 +0800 Subject: [PATCH 8/9] test: cover non-UTC front-matter dates --- test/scripts/processors/asset.ts | 31 +++++++++++++++++++++++++++++++ test/scripts/processors/post.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/test/scripts/processors/asset.ts b/test/scripts/processors/asset.ts index 1e78bc44b..e022a403a 100644 --- a/test/scripts/processors/asset.ts +++ b/test/scripts/processors/asset.ts @@ -662,4 +662,35 @@ describe('asset', () => { unlink(file.source) ]); }); + + it('page - timezone - non-UTC front-matter date', async () => { + const body = [ + 'title: "Hello world"', + 'date: 2014-04-24 01:32:21', + 'updated: 2015-05-05 02:03:04', + '---' + ].join('\n'); + + const file = newFile({ + path: 'hello.njk', + type: 'create', + renderable: true + }); + + hexo.config.timezone = 'Asia/Shanghai'; + + await writeFile(file.source, body); + await process(file); + const page = Page.findOne({source: file.path}); + const date = page.date.tz(hexo.config.timezone).format(dateFormat); + const updated = page.updated.tz(hexo.config.timezone).format(dateFormat); + + await Promise.all([ + page.remove(), + unlink(file.source) + ]); + + date.should.eql('2014-04-24 01:32:21'); + updated.should.eql('2015-05-05 02:03:04'); + }); }); diff --git a/test/scripts/processors/post.ts b/test/scripts/processors/post.ts index 43ef10a0d..184d29937 100644 --- a/test/scripts/processors/post.ts +++ b/test/scripts/processors/post.ts @@ -1331,6 +1331,36 @@ describe('post', () => { return unlink(file.source); }); + it('post - timezone - non-UTC front-matter date', async () => { + const body = [ + 'title: "Hello world"', + 'date: 2014-04-24 01:32:21', + 'updated: 2015-05-05 02:03:04', + '---' + ].join('\n'); + + const file = newFile({ + path: 'foo.html', + published: true, + type: 'create', + renderable: true + }); + + hexo.config.timezone = 'Asia/Shanghai'; + + await writeFile(file.source, body); + await process(file); + const post = Post.findOne({ source: file.path }); + const date = post.date.tz(hexo.config.timezone).format(dateFormat); + const updated = post.updated.tz(hexo.config.timezone).format(dateFormat); + + post.remove(); + await unlink(file.source); + + date.should.eql('2014-04-24 01:32:21'); + updated.should.eql('2015-05-05 02:03:04'); + }); + it('post - new_post_name timezone', async () => { const body = [ 'title: "Hello world"', From 3d476bf65f08e5408a83af9d8f9627a68cbd46ec Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Fri, 3 Jul 2026 15:07:44 +0800 Subject: [PATCH 9/9] fix: avoid duplicate timezone adjustment --- lib/plugins/processor/asset.ts | 6 ++++-- lib/plugins/processor/post.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/plugins/processor/asset.ts b/lib/plugins/processor/asset.ts index 1cd1223bb..6e733fc77 100644 --- a/lib/plugins/processor/asset.ts +++ b/lib/plugins/processor/asset.ts @@ -56,6 +56,8 @@ function processPage(ctx: Hexo, file: _File) { const data: PageSchema = yfm(content, { defaultTimeZone: config.timezone }); + const dateParsedWithTimezone = data.date instanceof Date; + const updatedParsedWithTimezone = data.updated instanceof Date; const output = ctx.render.getOutput(path); data.source = path; @@ -64,7 +66,7 @@ function processPage(ctx: Hexo, file: _File) { data.date = toDate(data.date) as any; if (data.date) { - if (timezone) data.date = adjustDateForTimezone(data.date, timezone) as any; + if (timezone && !dateParsedWithTimezone) data.date = adjustDateForTimezone(data.date, timezone) as any; } else { data.date = stats.ctime as any; } @@ -72,7 +74,7 @@ function processPage(ctx: Hexo, file: _File) { data.updated = toDate(data.updated) as any; if (data.updated) { - if (timezone) data.updated = adjustDateForTimezone(data.updated, timezone) as any; + if (timezone && !updatedParsedWithTimezone) data.updated = adjustDateForTimezone(data.updated, timezone) as any; } else if (updated_option === 'date') { data.updated = data.date; } else if (updated_option === 'empty') { diff --git a/lib/plugins/processor/post.ts b/lib/plugins/processor/post.ts index 5b9f4537a..35a9c21ba 100644 --- a/lib/plugins/processor/post.ts +++ b/lib/plugins/processor/post.ts @@ -95,6 +95,8 @@ function processPost(ctx: Hexo, file: _File) { const data: PostSchema = yfm(content, { defaultTimeZone: config.timezone }); + const dateParsedWithTimezone = data.date instanceof Date; + const updatedParsedWithTimezone = data.updated instanceof Date; const info = parseFilename(config.new_post_name, path); const keys = Object.keys(info); @@ -137,7 +139,7 @@ function processPost(ctx: Hexo, file: _File) { // Convert date and updated time from UTC to local time (based on timezone in Hexo config) if (data.date) { - if (timezone) data.date = adjustDateForTimezone(data.date, timezone) as any; + if (timezone && !dateParsedWithTimezone) data.date = adjustDateForTimezone(data.date, timezone) as any; } else { data.date = stats.birthtime as any; } @@ -145,7 +147,7 @@ function processPost(ctx: Hexo, file: _File) { data.updated = toDate(data.updated) as any; if (data.updated) { - if (timezone) data.updated = adjustDateForTimezone(data.updated, timezone) as any; + if (timezone && !updatedParsedWithTimezone) data.updated = adjustDateForTimezone(data.updated, timezone) as any; } else if (updated_option === 'date') { data.updated = data.date; } else if (updated_option === 'empty') {