[#6688] added optional timezone identifier argument to the JSVM DateTime constructor

This commit is contained in:
Gani Georgiev 2025-04-08 14:16:53 +03:00
parent 5d32d22ff5
commit 0efbbb0d10
5 changed files with 3378 additions and 3324 deletions

View File

@ -1,6 +1,6 @@
## v0.27.0 (WIP)
- ⚠️ Moved the Create and Manage API rule checks out of the `OnRecordCreateRequest` hook finalizer, **aka. now all API rules are checked BEFORE triggering their corresponding `*Request` hook**.
- ⚠️ Moved the Create and Manage API rule checks out of the `OnRecordCreateRequest` hook finalizer, **aka. now all CRUD API rules are checked BEFORE triggering their corresponding `*Request` hook**.
This was done to minimize the confusion regarding the firing order of the request operations, making it more predictable and consistent with the other record List/View/Update/Delete request actions.
It could be a minor breaking change if you are relying on the old behavior or have a Go `tests.ApiScenario` that is testing a Create API rule failure and expect `OnRecordCreateRequest` to be fired. In that case for example you may have to update your test scenario like:
```go
@ -28,6 +28,15 @@
- Forced `text/javascript` Content-Type when serving `.js`/`.mjs` collection uploaded files ([#6597](https://github.com/pocketbase/pocketbase/issues/6597)).
- Added optional timezone argument to the JSVM `DateTime` constructor to parse the date string in the specified TZ identifier in order to better handle the daylight saving time nuances ([#6688](https://github.com/pocketbase/pocketbase/discussions/6688)):
```
// the same as with CET offset: new DateTime("2025-10-26 03:00:00 +01:00")
new DateTime("2025-10-26 03:00:00", "Europe/Amsterdam") // 2025-10-26 02:00:00.000Z
// the same as with CEST offset: new DateTime("2025-10-26 01:00:00 +02:00")
new DateTime("2025-10-26 01:00:00", "Europe/Amsterdam") // 2025-10-25 23:00:00.000Z
```
- Soft-deprecated the `$http.send`'s `result.raw` field in favor of `result.body` that contains the response body as plain bytes slice to avoid the discrepancies between Go and the JSVM when casting binary data to string.
(@todo update docs to use the new field)

View File

@ -548,9 +548,18 @@ func baseBinds(vm *goja.Runtime) {
vm.Set("DateTime", func(call goja.ConstructorCall) *goja.Object {
instance := types.NowDateTime()
val, _ := call.Argument(0).Export().(string)
if val != "" {
instance, _ = types.ParseDateTime(val)
rawDate, _ := call.Argument(0).Export().(string)
locName, _ := call.Argument(1).Export().(string)
if rawDate != "" && locName != "" {
loc, err := time.LoadLocation(locName)
if err != nil {
loc = time.UTC
}
instance, _ = types.ParseDateTime(cast.ToTimeInDefaultLocation(rawDate, loc))
} else if rawDate != "" {
// forward directly to ParseDateTime to preserve the original behavior
instance, _ = types.ParseDateTime(rawDate)
}
instanceValue := vm.ToValue(instance).(*goja.Object)

View File

@ -568,15 +568,35 @@ func TestBaseBindsDateTime(t *testing.T) {
baseBinds(vm)
_, err := vm.RunString(`
const v0 = new DateTime();
if (v0.isZero()) {
throw new Error('Expected to fallback to now, got zero value');
const now = new DateTime();
if (now.isZero()) {
throw new Error('(now) Expected to fallback to now, got zero value');
}
const v1 = new DateTime('2023-01-01 00:00:00.000Z');
const expected = "2023-01-01 00:00:00.000Z"
if (v1.string() != expected) {
throw new Error('Expected ' + expected + ', got ', v1.string());
const nowPart = now.string().substring(0, 19)
const scenarios = [
// empty datetime string and no custom location
{date: new DateTime(''), expected: nowPart},
// empty datetime string and custom default location (should be ignored)
{date: new DateTime('', 'Asia/Tokyo'), expected: nowPart},
// full datetime string and no custom default location
{date: new DateTime('2023-01-01 00:00:00.000Z'), expected: "2023-01-01 00:00:00.000Z"},
// invalid location (fallback to UTC)
{date: new DateTime('2025-10-26 03:00:00', 'invalid'), expected: "2025-10-26 03:00:00.000Z"},
// CET
{date: new DateTime('2025-10-26 03:00:00', 'Europe/Amsterdam'), expected: "2025-10-26 02:00:00.000Z"},
// CEST
{date: new DateTime('2025-10-26 01:00:00', 'Europe/Amsterdam'), expected: "2025-10-25 23:00:00.000Z"},
// with timezone/offset in the date string (aka. should ignore the custom default location)
{date: new DateTime('2025-10-26 01:00:00 +0200', 'Asia/Tokyo'), expected: "2025-10-25 23:00:00.000Z"},
];
for (let i = 0; i < scenarios.length; i++) {
const s = scenarios[i];
if (!s.date.string().includes(s.expected)) {
throw new Error('(' + i + ') ' + s.date.string() + ' does not contain expected ' + s.expected);
}
}
`)
if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -608,19 +608,27 @@ declare class Timezone implements time.Location {
interface DateTime extends types.DateTime{} // merge
/**
* DateTime defines a single DateTime type instance.
* The returned date is always represented in UTC.
*
* Example:
*
* ` + "```" + `js
* const dt0 = new DateTime() // now
*
* // full datetime string
* const dt1 = new DateTime('2023-07-01 00:00:00.000Z')
*
* // datetime string with default "parse in" timezone location
* //
* // similar to new DateTime('2023-07-01 00:00:00 +01:00') or new DateTime('2023-07-01 00:00:00 +02:00')
* // but accounts for the daylight saving time (DST)
* const dt2 = new DateTime('2023-07-01 00:00:00', 'Europe/Amsterdam')
* ` + "```" + `
*
* @group PocketBase
*/
declare class DateTime implements types.DateTime {
constructor(date?: string)
constructor(date?: string, defaultParseInLocation?: string)
}
interface ValidationError extends ozzo_validation.Error{} // merge