From a8ca2ad49dbff4990c81ae9de61057da191f2d56 Mon Sep 17 00:00:00 2001 From: ddededodediamante Date: Wed, 20 May 2026 01:44:35 -0300 Subject: [PATCH 1/3] locale blocks & docs for date format v2 --- src/lib/Documentation/DateFormatV2.md | 189 ++++++++++++++++++ src/lib/Documentation/pages.js | 8 +- src/lib/extensions.js | 3 +- .../ddededodediamante/dateFormatV2.js | 123 +++++++++--- 4 files changed, 294 insertions(+), 29 deletions(-) create mode 100644 src/lib/Documentation/DateFormatV2.md diff --git a/src/lib/Documentation/DateFormatV2.md b/src/lib/Documentation/DateFormatV2.md new file mode 100644 index 000000000..a5458126b --- /dev/null +++ b/src/lib/Documentation/DateFormatV2.md @@ -0,0 +1,189 @@ +# Date Format + +Date Format is an extension that lets you create, format, compare, and manipulate dates. + +## Blocks + +NOTE: The ticket shapes cannot be displayed here, they show up as circular shapes. + +### Creation & Format + +```scratch +current date::#59c074 reporter +``` + +Returns the current date. + +--- + +```scratch +new date from [2025-03-12]::#59c074 reporter +``` + +Creates a date value from a string. Accepts most common date formats (e.g. `2025-03-12`, `March 12 2025`, `12/03/2025`). If the string cannot be parsed, it returns an Invalid date. + +--- + +```scratch +format [date] as [dddd, MMMM D, YYYY]::#59c074 reporter +``` + +Formats a date using a custom pattern string. Tokens in the pattern are replaced with the corresponding date parts. Unrecognised characters aren't modified. + +| Token | Output example | Meaning | +| ------ | -------------- | ------------------------- | +| `YYYY` | `2025` | 4-digit year | +| `YY` | `25` | 2-digit year | +| `MMMM` | `January` | Full month name | +| `MMM` | `Jan` | Short month name | +| `MM` | `01` | Month, zero-padded | +| `M` | `1` | Month, no padding | +| `DD` | `09` | Day of month, zero-padded | +| `D` | `9` | Day of month, no padding | +| `dddd` | `Wednesday` | Full weekday name | +| `ddd` | `Wed` | Short weekday name | +| `HH` | `14` | Hour (24h), zero-padded | +| `H` | `14` | Hour (24h), no padding | +| `hh` | `02` | Hour (12h), zero-padded | +| `h` | `2` | Hour (12h), no padding | +| `mm` | `05` | Minutes, zero-padded | +| `m` | `5` | Minutes, no padding | +| `ss` | `08` | Seconds, zero-padded | +| `s` | `8` | Seconds, no padding | +| `A` | `AM` / `PM` | AM/PM indicator | +| `a` | `am` / `pm` | am/pm indicator | +| `SSS` | `042` | Milliseconds, zero-padded | +| `Z` | `+02:00` | UTC timezone offset | + +--- + +```scratch +format [date] as [short v] locale::#59c074 reporter +``` + +Formats a date using the active locale. + +Examples: +- `short` looks like `Wed, May 20, 2026` +- `long` looks like `Wednesday, May 20, 2026` + +--- + +```scratch +format [date] as [relative v]::#59c074 reporter +``` + +Formats a date using a special format type. + +Examples: +- `relative` looks like `3 days ago` or `in 2 hours` +- `ISO string` looks like `2025-03-12T14:05:00.000Z` + +--- + +### Comparisons + +```scratch +is [date] valid?::#59c074 boolean +``` + +Checks if the date is a valid date value. + +--- + +```scratch +is [date1] [after v] [date2]?::#59c074 boolean +``` + +Compares two dates based on the chosen operation. + +--- + +```scratch +is [date] [weekend v]?::#59c074 boolean +``` + +Checks a specific property of a date. Options are: + +- `weekend` = if the day is Saturday or Sunday +- `weekday` = if the day is Monday through Friday +- `today` = if the date is in the same day as today +- `yesterday` = if the date is in the same day as yesterday +- `tomorrow` = if the day is in the same day as tomorrow +- `first of month` = if the day of the month is 1 +- `last of month` = if the day is the last day of its month +- `leap year` = if the year is a leap year + +--- + +### Operators + +```scratch +get [days v] between [date1] and [date2]::#59c074 reporter +``` + +Returns the absolute difference between two dates in the given unit. + +--- + +```scratch +get [year v] of [date]::#59c074 reporter +``` + +Extracts a component from a date with local time. + +| Part | Returns | +| -------------- | ------------------------------ | +| `milliseconds` | 0-999 | +| `seconds` | 0-59 | +| `minutes` | 0-59 | +| `hours` | 0-23 | +| `day (week)` | 0 (Sunday) - 6 (Saturday) | +| `day (month)` | 1-31 | +| `day (year)` | 1-366 | +| `month` | 1-12 | +| `year` | e.g. `2025` | +| `time` | Unix timestamp in milliseconds | + +--- + +```scratch +set [year v] of [date] to [2026]::#59c074 reporter +``` + +Returns a new date with one component replaced by the given value. +Setting `weekday` shifts the date to the matching day within the same week. + +--- + +```scratch +add [1] [days v] to [date]::#59c074 reporter +``` + +Returns a new date with the specified amount of time added. Use a negative number to subtract. + +--- + +```scratch +round [date] to nearest [minutes v]::#59c074 reporter +``` + +Returns a new date rounded to the nearest given unit. For example, rounding `14:08` to `minutes` gives `14:08:00.000`, while rounding to `hours` gives `14:00` or `15:00` depending on which is closer. + +--- + +### Locale + +```scratch +set locale to [English (US) v]::#59c074 +``` + +Sets the locale used when formatting dates with the *format as locale* block. The option `browser default` uses whatever language the user's browser is set to. This setting is saved with the project. + +--- + +```scratch +current locale::#59c074 reporter +``` + +Returns the currently active locale code (e.g. `en-US`), or `default` if none has been set. diff --git a/src/lib/Documentation/pages.js b/src/lib/Documentation/pages.js index 313b5d162..fc6210ef7 100644 --- a/src/lib/Documentation/pages.js +++ b/src/lib/Documentation/pages.js @@ -37,9 +37,11 @@ import PaintUtils from "./PaintUtils.md?raw"; import Resolution from "./Resolution.md?raw"; // Project Interfaces - import ProjectInterfaces from "./ProjectInterfaces.md?raw"; +// Date Format V2 +import ProjectInterfaces from "./DateFormatV2.md?raw"; + export default { // the key is the path to the docs page // so you can do "sharkpool-particle-tools" for example @@ -77,5 +79,7 @@ export default { "Resolution": Resolution, // Project Interfaces - "ProjectInterfaces": ProjectInterfaces + "ProjectInterfaces": ProjectInterfaces, + + "DateFormatV2": DateFormatV2 }; diff --git a/src/lib/extensions.js b/src/lib/extensions.js index d1e8f0ab9..64960b1b0 100644 --- a/src/lib/extensions.js +++ b/src/lib/extensions.js @@ -260,7 +260,8 @@ export default [ code: "ddededodediamante/dateFormatV2.js", banner: "ddededodediamante/dateFormat.svg", creator: "ddededodediamante", - tags: ["customtype", "utility", "small", "data", "time"], + documentation: "DateFormatV2", + tags: ["customtype", "utility", "small", "data", "time", "text"], isGitHub: true, }, { diff --git a/static/extensions/ddededodediamante/dateFormatV2.js b/static/extensions/ddededodediamante/dateFormatV2.js index c1732be3b..e0aae2e5d 100644 --- a/static/extensions/ddededodediamante/dateFormatV2.js +++ b/static/extensions/ddededodediamante/dateFormatV2.js @@ -54,7 +54,7 @@ } class ddeDateType { - customId = "ddeDateFormat.date" + customId = "ddeDateFormat.date"; constructor(dateInput) { this._date = @@ -66,7 +66,7 @@ } isValid() { - return this._date instanceof Date && !isNaN(this._date.getTime()); + return !isNaN(this._date.getTime()); } toDate() { return new Date(this._date.getTime()); @@ -99,7 +99,7 @@ const detail = element( "span", - this._date.toLocaleDateString(undefined, { + this._date.toLocaleDateString(activeLocale, { weekday: "short", year: "numeric", month: "short", @@ -124,12 +124,9 @@ flexDirection: "column", alignItems: "center", }); - const top = element( - "div", - this.isValid() ? this._prettyShort() : "Invalid Date", - {}, - ); - const sub = element("small", this.isValid() ? this.toString() : "", { + const valid = this.isValid(); + const top = element("div", valid ? this._prettyShort() : "Invalid Date", {}); + const sub = element("small", valid ? this.toString() : "", { opacity: "0.7", }); @@ -242,9 +239,10 @@ } function addToDate(d, amount, unit) { - if (isNaN(d?.getTime())) throw new Error("Invalid Date"); + const time = d?.getTime(); + if (isNaN(time)) throw new Error("Invalid Date"); - const result = new Date(d.getTime()); + const result = new Date(time); amount = Number(amount) || 0; switch ((unit || "").toLowerCase()) { case "milliseconds": @@ -273,9 +271,11 @@ } function diffDates(d1, d2, unit) { - if (isNaN(d1?.getTime()) || isNaN(d2?.getTime())) throw new Error("Invalid Date"); + const t1 = d1?.getTime(); + const t2 = d2?.getTime(); + if (isNaN(t1) || isNaN(t2)) throw new Error("Invalid Date"); - const ms = d1.getTime() - d2.getTime(); + const ms = t1 - t2; const absMs = Math.abs(ms); switch ((unit || "").toLowerCase()) { @@ -311,7 +311,17 @@ }, ); + let activeLocale = undefined; + class ddeDateExtension { + serialize() { + return { activeLocale }; + } + + deserialize(data) { + activeLocale = data.activeLocale; + } + getInfo() { return { id: "ddeDateFormatV2", @@ -476,6 +486,23 @@ }, ...ddeDateFormat.BlockOutput, }, + { blockType: Scratch.BlockType.LABEL, text: "Locale" }, + { + opcode: "setLocale", + blockType: Scratch.BlockType.COMMAND, + text: "set locale to [locale]", + arguments: { + locale: { + type: Scratch.ArgumentType.STRING, + menu: "locales", + }, + }, + }, + { + opcode: "getLocale", + blockType: Scratch.BlockType.REPORTER, + text: "current locale", + }, ], menus: { compareOperations: { @@ -537,18 +564,58 @@ { text: "ISO string", value: "iso" }, ], }, + locales: { + acceptReporters: true, + items: [ + { text: "browser default", value: "default" }, + { text: "English (US)", value: "en-US" }, + { text: "English (UK)", value: "en-GB" }, + { text: "Spanish (ES)", value: "es-ES" }, + { text: "Spanish (MX)", value: "es-MX" }, + { text: "French (FR)", value: "fr-FR" }, + { text: "German (DE)", value: "de-DE" }, + { text: "Italian (IT)", value: "it-IT" }, + { text: "Portuguese (BR)", value: "pt-BR" }, + { text: "Portuguese (PT)", value: "pt-PT" }, + { text: "Dutch (NL)", value: "nl-NL" }, + { text: "Polish (PL)", value: "pl-PL" }, + { text: "Russian (RU)", value: "ru-RU" }, + { text: "Japanese (JP)", value: "ja-JP" }, + { text: "Chinese (CN)", value: "zh-CN" }, + { text: "Chinese (TW)", value: "zh-TW" }, + { text: "Korean (KR)", value: "ko-KR" }, + { text: "Arabic (SA)", value: "ar-SA" }, + { text: "Hindi (IN)", value: "hi-IN" }, + { text: "Turkish (TR)", value: "tr-TR" }, + { text: "Swedish (SE)", value: "sv-SE" }, + { text: "Norwegian (NO)", value: "nb-NO" }, + { text: "Danish (DK)", value: "da-DK" }, + { text: "Finnish (FI)", value: "fi-FI" }, + { text: "Greek (GR)", value: "el-GR" }, + { text: "Hebrew (IL)", value: "he-IL" }, + { text: "Indonesian (ID)", value: "id-ID" }, + { text: "Thai (TH)", value: "th-TH" }, + { text: "Vietnamese (VN)", value: "vi-VN" }, + ], + }, }, }; } + setLocale({ locale }) { + activeLocale = locale === "default" ? undefined : locale; + } + + getLocale() { + return activeLocale || "default"; + } + toDateType(input) { return input instanceof ddeDateType ? input : new ddeDateType(input); } toNativeDate(input) { - if (input instanceof ddeDateType && input.isValid()) return input.toDate(); - const d = this.toDateType(input); - return d.toDate(); + return this.toDateType(input).toDate(); } currentDate() { @@ -569,7 +636,7 @@ const d = this.toNativeDate(date); if (isNaN(d.getTime())) throw new Error("Invalid Date"); - return d.toLocaleDateString(undefined, { + return d.toLocaleDateString(activeLocale, { weekday: type, year: "numeric", month: type, @@ -600,15 +667,17 @@ compareDate({ date1, date2, operation }) { const d1 = this.toNativeDate(date1); const d2 = this.toNativeDate(date2); - if (isNaN(d1.getTime()) || isNaN(d2.getTime())) throw new Error("Invalid Date"); + const t1 = d1.getTime(); + const t2 = d2.getTime(); + if (isNaN(t1) || isNaN(t2)) throw new Error("Invalid Date"); switch (operation) { case "after": - return d1.getTime() > d2.getTime(); + return t1 > t2; case "before": - return d1.getTime() < d2.getTime(); + return t1 < t2; case "same": - return d1.getTime() === d2.getTime(); + return t1 === t2; default: return false; } @@ -740,6 +809,7 @@ const ms = d.getTime(); let rounded; + const unitLower = unit.toLowerCase(); const unitMs = { milliseconds: 1, @@ -747,14 +817,14 @@ minutes: 60000, hours: 3600000, days: 86400000, - }[unit.toLowerCase()]; + }[unitLower]; if (unitMs) { rounded = Math.round(ms / unitMs) * unitMs; - } else if (unit.toLowerCase() === "months") { + } else if (unitLower === "months") { const temp = new Date(d.getFullYear(), d.getMonth() + 0.5, 1); rounded = temp.getTime(); - } else if (unit.toLowerCase() === "years") { + } else if (unitLower === "years") { const temp = new Date(d.getFullYear() + 0.5, 0, 1); rounded = temp.getTime(); } else { @@ -769,6 +839,7 @@ if (isNaN(d)) throw new Error("Invalid Date"); const now = new Date(); + const day = d.getDay(); const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && @@ -776,9 +847,9 @@ switch (property) { case "weekend": - return [0, 6].includes(d.getDay()); + return day === 0 || day === 6; case "weekday": - return ![0, 6].includes(d.getDay()); + return day !== 0 && day !== 6; case "today": return sameDay(d, now); case "yesterday": From 96cc7a338300620d488d57661e30d7157f1d3b77 Mon Sep 17 00:00:00 2001 From: ddededodediamante Date: Wed, 20 May 2026 01:45:42 -0300 Subject: [PATCH 2/3] ooooops --- src/lib/Documentation/pages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Documentation/pages.js b/src/lib/Documentation/pages.js index fc6210ef7..546137a0f 100644 --- a/src/lib/Documentation/pages.js +++ b/src/lib/Documentation/pages.js @@ -40,7 +40,7 @@ import Resolution from "./Resolution.md?raw"; import ProjectInterfaces from "./ProjectInterfaces.md?raw"; // Date Format V2 -import ProjectInterfaces from "./DateFormatV2.md?raw"; +import DateFormatV2 from "./DateFormatV2.md?raw"; export default { // the key is the path to the docs page From c22463849b4ae915578ff4c6cb900123f2a73d54 Mon Sep 17 00:00:00 2001 From: ddededodediamante Date: Wed, 20 May 2026 01:51:06 -0300 Subject: [PATCH 3/3] we dont need that Lol --- src/lib/Documentation/DateFormatV2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Documentation/DateFormatV2.md b/src/lib/Documentation/DateFormatV2.md index a5458126b..df04205e4 100644 --- a/src/lib/Documentation/DateFormatV2.md +++ b/src/lib/Documentation/DateFormatV2.md @@ -103,7 +103,7 @@ Compares two dates based on the chosen operation. is [date] [weekend v]?::#59c074 boolean ``` -Checks a specific property of a date. Options are: +Checks a specific property of a date. - `weekend` = if the day is Saturday or Sunday - `weekday` = if the day is Monday through Friday