Unplanned
Last Updated: 03 Aug 2026 09:43 by ADMIN
Svitlana
Created on: 27 Jul 2026 14:38
Category: Kendo UI for Angular
Type: Bug Report
0
Incorrect Formatting of Timezone Offsets Containing Non-Zero Minutes

`@progress/kendo-intl` incorrectly formats timezone offsets whose minute component is non-zero.

For example, a JavaScript `Date` with the historical Warsaw offset `UTC+01:24` is formatted as `+01:04` instead of `+01:24`.

This affects all tested `Z`, `X`, and `x` timezone format variants.

Environment

- `@progress/kendo-angular-intl`: `24.2.0`
- `@progress/kendo-intl`: `3.2.1`

- Browser/Node timezone: `Europe/Warsaw`
- Operating system: Windows
- Format: `yyyy-MM-ddTHH:mm:ssZZZZZ`

The same formatting implementation appears in `@progress/kendo-intl` versions `3.1.2` and `3.2.1`.

Reproduction

const { formatDate } = require('@progress/kendo-intl');

process.env.TZ = 'Europe/Warsaw';

const date = new Date(1900, 0, 1, 16, 0, 0);

console.log(date.toString());
console.log(date.getTimezoneOffset());
console.log(formatDate(date, 'yyyy-MM-ddTHH:mm:ssZZZZZ'));

Actual Result

Mon Jan 01 1900 16:00:00 GMT+0124
-84
1900-01-01T16:00:00+01:04

Expected Result

1900-01-01T16:00:00+01:24

Date#getTimezoneOffset() returns -84, meaning the local timezone is 84 minutes ahead of UTC:

84 minutes = 1 hour 24 minutes

Other Examples

The issue is not limited to positive offsets:

getTimezoneOffset()ExpectedActual
-84+01:24+01:04
210-03:30-03:05

UTC is also formatted differently depending on the token, but that behavior may be intentional:

OffsetExpected ISO representationZZZZZ result
0+00:00 or ZZ

Tested Format Specifiers

All applicable format variants use the same incorrect minute calculation:

Z      -> +0104
ZZ     -> +0104
ZZZ    -> +0104
ZZZZ   -> GMT+01:04
ZZZZZ  -> +01:04

X      -> +0104
XX     -> +0104
XXX    -> +01:04
XXXX   -> +0104
XXXXX  -> +01:04

x      -> +0104
xxx    -> +01:04
xxxxx  -> +01:04

Changing from ZZZZZ to XXXXX or XXX therefore does not resolve the problem.

Suspected Root Cause

The current formatter appears to perform approximately this calculation:

const offset = date.getTimezoneOffset() / 60;
const hoursMinutes = Math.abs(offset).toString().split('.');
const minutes = hoursMinutes[1] || 0;

For an 84-minute offset:

84 / 60 = 1.4

The decimal portion "4" is then treated as four minutes and padded to "04".

However, the fractional part represents a fraction of an hour:

0.4 hours * 60 = 24 minutes

Likewise, for 210 minutes:

210 / 60 = 3.5

The formatter produces 03:05, although 0.5 hours is 30 minutes.

Suggested Fix

Timezone offsets should remain integer minute values throughout formatting:

function formatTimeZone(date, info, options) {
    const totalMinutes = date.getTimezoneOffset();
    const absoluteMinutes = Math.abs(totalMinutes);
    const hours = Math.floor(absoluteMinutes / 60);
    const minutes = absoluteMinutes % 60;
    const sign = totalMinutes <= 0 ? '+' : '-';

    // Continue applying shortHours, separator, optionalMinutes,
    // localizedName and zZeroOffset options using hours and minutes.
}

The important calculations are:

const hours = Math.floor(Math.abs(offsetMinutes) / 60);
const minutes = Math.abs(offsetMinutes) % 60;

Converting the offset to a decimal hour and splitting its string representation is not reliable.

Suggested Regression Tests

it('formats a positive timezone offset with minutes', () => {
    const date = new Date(2024, 5, 25, 12, 7, 5);
    vi.spyOn(date, 'getTimezoneOffset').mockReturnValue(-84);

    expect(formatDate(date, 'yyyy-MM-ddTHH:mm:ssZZZZZ'))
        .toBe('2024-06-25T12:07:05+01:24');
});

it('formats a negative timezone offset with minutes', () => {
    const date = new Date(2024, 5, 25, 12, 7, 5);
    vi.spyOn(date, 'getTimezoneOffset').mockReturnValue(210);

    expect(formatDate(date, 'yyyy-MM-ddTHH:mm:ssZZZZZ'))
        .toBe('2024-06-25T12:07:05-03:30');
});

Other useful cases include:

-345 -> +05:45
-330 -> +05:30
210 -> -03:30
0 -> Z or +00:00, depending on the selected token

Impact

This can corrupt serialized date-time values when they are passed to systems that honor the emitted offset, including .NET DateTimeOffset.

Example:

Intended: 1900-01-01T16:00:00+01:24
Emitted:  1900-01-01T16:00:00+01:04

These values represent instants 20 minutes apart.

The issue affects:

  • Historical dates from regions that previously used local mean time.
  • Current timezones with half-hour or quarter-hour offsets.
  • Any test or custom Date implementation returning a non-whole-hour offset.

Examples of modern fractional offsets include UTC+05:30, UTC+05:45, UTC+09:30, and UTC-03:30.

Additional Context

We initially encountered this with Europe/Warsaw and 1900-01-01. The JavaScript runtime correctly reports Warsaw's historical offset as UTC+01:24. The incorrect UTC+01:04 value is introduced only during Kendo formatting.

We have implemented a temporary application-level workaround that formats the date portion with Kendo and calculates the timezone offset directly from integer minutes.

StackBlitz

 

1 comment
ADMIN
Martin Bechev
Posted on: 03 Aug 2026 09:43

Hi Svitlana,

Thank you for your detailed analysis and for outlining both the root cause and suggested fix for the timezone offset formatting issue in @progress/kendo-intl. Your understanding of the problem is correct: the formatter currently miscalculates the minute component of the timezone offset, leading to incorrect formatted strings for non-whole-hour offsets.

The behavior you are seeing is a confirmed bug in @progress/kendo-intl. The formatTimeZone function divides the value returned by Date#getTimezoneOffset() by 60 to obtain a decimal hour (e.g., 84 / 60 = 1.4), then splits the decimal string to extract the minutes part. Because "1.4".split(".") yields "4" rather than 24, the formatter emits +01:04 instead of the correct +01:24. The same arithmetic flaw affects all timezone format tokens (Z, X, x, and their length variants).

This has been tracked as an open issue:

https://github.com/telerik/kendo-intl/issues/128

and the fix needs to land in the @progress/kendo-intl package before it propagates to @progress/kendo-angular-intl. I also updated the issue with the info provided in this ticket.

As of now, there is no built-in workaround or configuration in Kendo UI for Angular or @progress/kendo-intl that would resolve this issue automatically. Your current application-level workaround—formatting the date with Kendo and then calculating the offset from integer minutes—is the most reliable solution until an official fix is released. There are no alternative approaches within the Kendo UI libraries themselves that would circumvent this formatting bug at this time.

Regards,


Martin Bechev
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.