Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(template): add date template helper functions #5997

Merged
merged 15 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"types": "build/src/index.d.ts",
"dependencies": {
"@codenamize/codenamize": "^1.1.1",
"@date-fns/utc": "^1.2.0",
"@hapi/joi": "git+https://github.com/garden-io/joi.git#master",
"@jsdevtools/readdir-enhanced": "^6.0.4",
"@kubernetes/client-node": "^1.0.0-rc4",
Expand Down
127 changes: 127 additions & 0 deletions core/src/template-string/date-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright (C) 2018-2024 Garden Technologies, Inc. <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import type { TemplateHelperFunction } from "./functions.js"
import { joi } from "../config/common.js"
import { format as formatFns, add, type Duration } from "date-fns"
import { UTCDateMini } from "@date-fns/utc"

type ShiftDateTimeUnit = keyof Duration
const validShiftDateTimeUnits: ShiftDateTimeUnit[] = [
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
] as const

const validModifyDateTimeUnits = ["years", "months", "days", "hours", "minutes", "seconds", "milliseconds"] as const
type ModifyDateTimeUnit = (typeof validModifyDateTimeUnits)[number]
// This is still type-safe because every entry of ModifyDateTimeUnit must be declared in the index below.
const modifyDateFunctions: { [k in ModifyDateTimeUnit]: (date: Date, timeUnits: number) => void } = {
years: (date, timeUnits) => date.setUTCFullYear(timeUnits),
months: (date, timeUnits) => date.setUTCMonth(timeUnits),
days: (date, timeUnits) => date.setUTCDate(timeUnits),
hours: (date, timeUnits) => date.setUTCHours(timeUnits),
minutes: (date, timeUnits) => date.setUTCMinutes(timeUnits),
seconds: (date, timeUnits) => date.setUTCSeconds(timeUnits),
milliseconds: (date, timeUnits) => date.setUTCMilliseconds(timeUnits),
} as const

const timeZoneComment =
"The input date is always converted to the UTC time zone before the modification. If no explicit timezone is specified on the input date, then the system default one will be used. The output date is always returned in the UTC time zone too."

export const dateHelperFunctionSpecs: TemplateHelperFunction[] = [
{
name: "formatDateUtc",
description: `Formats the given date using the specified format. ${timeZoneComment}`,
arguments: {
date: joi.string().required().description("The date to format."),
format: joi
.string()
.required()
.description("The format to use. See https://date-fns.org/v2.21.1/docs/format for details."),
},
outputSchema: joi.string(),
exampleArguments: [
{ input: ["2021-01-01T00:00:00Z", "yyyy-MM-dd"], output: "2021-01-01" },
{ input: ["2021-01-01T00:00:00+0200", "yyyy-MM-dd"], output: "2020-12-31" },
{ input: ["2021-01-01T00:00:00Z", "yyyy-MM-dd HH:mm:ss"], output: "2021-01-01 00:00:00" },
{ input: ["2021-01-01T00:00:00+0200", "yyyy-MM-dd HH:mm:ss"], output: "2020-12-31 22:00:00" },
],
fn: (date: string, format: string) => {
const utcDate = new UTCDateMini(date)
return formatFns(utcDate, format)
},
},
{
name: "shiftDateUtc",
description: `Shifts the date by the specified amount of time units. ${timeZoneComment}`,
arguments: {
date: joi.string().required().description("The date to shift."),
amount: joi.number().required().description("The amount of time units to shift the date by."),
unit: joi
.string()
.valid(...validShiftDateTimeUnits)
.required()
.description("The time unit to shift the date by."),
},
outputSchema: joi.string(),
exampleArguments: [
{ input: ["2021-01-01T00:00:00Z", 1, "seconds"], output: "2021-01-01T00:00:01.000Z" },
{ input: ["2021-01-01T00:00:00Z", -1, "seconds"], output: "2020-12-31T23:59:59.000Z" },
{ input: ["2021-01-01T00:00:00Z", 1, "minutes"], output: "2021-01-01T00:01:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", -1, "minutes"], output: "2020-12-31T23:59:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", 1, "hours"], output: "2021-01-01T01:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", -1, "hours"], output: "2020-12-31T23:00:00.000Z" },
{ input: ["2021-01-01T10:00:00+0200", 1, "hours"], output: "2021-01-01T09:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", 1, "days"], output: "2021-01-02T00:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", -1, "days"], output: "2020-12-31T00:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", 1, "months"], output: "2021-02-01T00:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", -1, "months"], output: "2020-12-01T00:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", 1, "years"], output: "2022-01-01T00:00:00.000Z" },
{ input: ["2021-01-01T00:00:00Z", -1, "years"], output: "2020-01-01T00:00:00.000Z" },
],
fn: (date: string, timeUnitAmount: number, unit: ShiftDateTimeUnit) => {
const dateClone = new Date(date)
return add(dateClone, { [unit]: timeUnitAmount }).toISOString()
},
},
{
name: "modifyDateUtc",
description: `Modifies the date by setting the specified amount of time units. ${timeZoneComment}`,
arguments: {
date: joi.string().required().description("The date to modify."),
amount: joi.number().required().description("The amount of time units to set."),
unit: joi
.string()
.valid(...validModifyDateTimeUnits)
.required()
.description("The time unit to set."),
},
outputSchema: joi.string(),
exampleArguments: [
{ input: ["2021-01-01T00:00:00.234Z", 345, "milliseconds"], output: "2021-01-01T00:00:00.345Z" },
{ input: ["2021-01-01T00:00:05Z", 30, "seconds"], output: "2021-01-01T00:00:30.000Z" },
{ input: ["2021-01-01T00:01:00Z", 15, "minutes"], output: "2021-01-01T00:15:00.000Z" },
{ input: ["2021-01-01T12:00:00Z", 11, "hours"], output: "2021-01-01T11:00:00.000Z" },
{ input: ["2021-01-01T10:00:00+0200", 11, "hours"], output: "2021-01-01T11:00:00.000Z" },
{ input: ["2021-01-31T00:00:00Z", 1, "days"], output: "2021-01-01T00:00:00.000Z" },
{ input: ["2021-03-01T00:00:00Z", 0, "months"], output: "2021-01-01T00:00:00.000Z" }, // 0 (Jan) - 11 (Dec)
{ input: ["2021-01-01T00:00:00Z", 2024, "years"], output: "2024-01-01T00:00:00.000Z" },
],
fn: (date: string, timeUnitAmount: number, unit: ModifyDateTimeUnit) => {
const dateClone = new Date(date)
const dateModifier = modifyDateFunctions[unit]
dateModifier(dateClone, timeUnitAmount)
return dateClone.toISOString()
},
},
]
10 changes: 6 additions & 4 deletions core/src/template-string/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,21 @@ import { load, loadAll } from "js-yaml"
import { safeDumpYaml } from "../util/serialization.js"
import indentString from "indent-string"
import { mayContainTemplateString } from "./template-string.js"
import { dateHelperFunctionSpecs } from "./date-functions.js"

interface ExampleArgument {
input: any[]
output: any // Used to validate expected output
input: unknown[]
output: unknown // Used to validate expected output
skipTest?: boolean
}

interface TemplateHelperFunction {
export interface TemplateHelperFunction {
name: string
description: string
arguments: { [name: string]: Joi.Schema }
outputSchema: Joi.Schema
exampleArguments: ExampleArgument[]
fn: Function
fn: (...args: any[]) => unknown
}

const helperFunctionSpecs: TemplateHelperFunction[] = [
Expand Down Expand Up @@ -407,6 +408,7 @@ const helperFunctionSpecs: TemplateHelperFunction[] = [
}
},
},
...dateHelperFunctionSpecs,
]

interface ResolvedHelperFunction extends TemplateHelperFunction {
Expand Down
52 changes: 52 additions & 0 deletions docs/reference/template-strings/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ Examples:
* `${concat([1,2,3], [4,5])}` -> `[1,2,3,4,5]`
* `${concat("string1", "string2")}` -> `"string1string2"`

## formatDateUtc

Formats the given date using the specified format. The input date is always converted to the UTC time zone before the modification. If no explicit timezone is specified on the input date, then the system default one will be used. The output date is always returned in the UTC time zone too.

Usage: `formatDateUtc(date, format)`

Examples:

* `${formatDateUtc("2021-01-01T00:00:00Z", "yyyy-MM-dd")}` -> `"2021-01-01"`
* `${formatDateUtc("2021-01-01T00:00:00+0200", "yyyy-MM-dd")}` -> `"2020-12-31"`
* `${formatDateUtc("2021-01-01T00:00:00Z", "yyyy-MM-dd HH:mm:ss")}` -> `"2021-01-01 00:00:00"`
* `${formatDateUtc("2021-01-01T00:00:00+0200", "yyyy-MM-dd HH:mm:ss")}` -> `"2020-12-31 22:00:00"`

## indent

Indents each line in the given string with the specified number of spaces.
Expand Down Expand Up @@ -134,6 +147,23 @@ Examples:

* `${lower("Some String")}` -> `"some string"`

## modifyDateUtc

Modifies the date by setting the specified amount of time units. The input date is always converted to the UTC time zone before the modification. If no explicit timezone is specified on the input date, then the system default one will be used. The output date is always returned in the UTC time zone too.

Usage: `modifyDateUtc(date, amount, unit)`

Examples:

* `${modifyDateUtc("2021-01-01T00:00:00.234Z", 345, "milliseconds")}` -> `"2021-01-01T00:00:00.345Z"`
* `${modifyDateUtc("2021-01-01T00:00:05Z", 30, "seconds")}` -> `"2021-01-01T00:00:30.000Z"`
* `${modifyDateUtc("2021-01-01T00:01:00Z", 15, "minutes")}` -> `"2021-01-01T00:15:00.000Z"`
* `${modifyDateUtc("2021-01-01T12:00:00Z", 11, "hours")}` -> `"2021-01-01T11:00:00.000Z"`
* `${modifyDateUtc("2021-01-01T10:00:00+0200", 11, "hours")}` -> `"2021-01-01T11:00:00.000Z"`
* `${modifyDateUtc("2021-01-31T00:00:00Z", 1, "days")}` -> `"2021-01-01T00:00:00.000Z"`
* `${modifyDateUtc("2021-03-01T00:00:00Z", 0, "months")}` -> `"2021-01-01T00:00:00.000Z"`
* `${modifyDateUtc("2021-01-01T00:00:00Z", 2024, "years")}` -> `"2024-01-01T00:00:00.000Z"`

## replace

Replaces all occurrences of a given substring in a string.
Expand All @@ -155,6 +185,28 @@ Examples:

* `${sha256("Some String")}` -> `"7f0fd64653ba0bb1a579ced2b6bf375e916cc60662109ee0c0b24f0a750c3a6c"`

## shiftDateUtc

Shifts the date by the specified amount of time units. The input date is always converted to the UTC time zone before the modification. If no explicit timezone is specified on the input date, then the system default one will be used. The output date is always returned in the UTC time zone too.

Usage: `shiftDateUtc(date, amount, unit)`

Examples:

* `${shiftDateUtc("2021-01-01T00:00:00Z", 1, "seconds")}` -> `"2021-01-01T00:00:01.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", -1, "seconds")}` -> `"2020-12-31T23:59:59.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", 1, "minutes")}` -> `"2021-01-01T00:01:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", -1, "minutes")}` -> `"2020-12-31T23:59:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", 1, "hours")}` -> `"2021-01-01T01:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", -1, "hours")}` -> `"2020-12-31T23:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T10:00:00+0200", 1, "hours")}` -> `"2021-01-01T09:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", 1, "days")}` -> `"2021-01-02T00:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", -1, "days")}` -> `"2020-12-31T00:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", 1, "months")}` -> `"2021-02-01T00:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", -1, "months")}` -> `"2020-12-01T00:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", 1, "years")}` -> `"2022-01-01T00:00:00.000Z"`
* `${shiftDateUtc("2021-01-01T00:00:00Z", -1, "years")}` -> `"2020-01-01T00:00:00.000Z"`

## slice

Slices a string or array at the specified start/end offsets. Note that you can use a negative number for the end offset to count backwards from the end.
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.