zuzu-js 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,12 @@
2
2
 
3
3
  class BigNum {
4
4
  constructor( value, text = null, isInt = null ) {
5
+ if ( typeof value === 'bigint' ) {
6
+ this._value = value;
7
+ this._text = text == null ? String( value ) : String( text );
8
+ this._isInt = isInt == null ? true : Boolean( isInt );
9
+ return;
10
+ }
5
11
  this._value = Number( value ?? 0 );
6
12
  this._text = text == null ? String( this._value ) : String( text );
7
13
  this._isInt = isInt == null ? Number.isInteger( this._value ) : Boolean( isInt );
@@ -19,22 +25,54 @@ class BigNum {
19
25
 
20
26
  static from_dec( value ) {
21
27
  const text = String( value ?? '0' ).trim();
22
- return new BigNum( Number( text || '0' ), text || '0', !/[.eE]/.test( text ) );
28
+ const safeText = text || '0';
29
+ if ( BigNum._isIntegerText( safeText ) ) {
30
+ return new BigNum( BigInt( safeText ), safeText, true );
31
+ }
32
+ return new BigNum( Number( safeText ), safeText, !/[.eE]/.test( safeText ) );
23
33
  }
24
34
 
25
35
  static from_hex( value ) {
26
- const text = String( value ?? '0' ).trim().toLowerCase().replace( /^0x/u, '' ) || '0';
27
- const parsed = Number.parseInt( text, 16 );
36
+ const text = String( value ?? '0' ).trim().replace( /^0x/u, '' ).toLowerCase() || '0';
37
+ const negative = text.startsWith( '-' );
38
+ const digits = text.replace( /^[+-]/u, '' );
39
+ const parsed = BigInt( `0x${digits}` ) * ( negative ? -1n : 1n );
28
40
  return new BigNum( parsed, String( parsed ), true );
29
41
  }
30
42
 
31
- get is_int() {
43
+ is_int() {
32
44
  return this._isInt;
33
45
  }
34
46
 
47
+ _toBigInt() {
48
+ if ( !this._isInt ) {
49
+ return null;
50
+ }
51
+ if ( typeof this._value === 'bigint' ) {
52
+ return this._value;
53
+ }
54
+ try {
55
+ return BigInt( this._text );
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ _toNumber() {
63
+ return typeof this._value === 'bigint' ? Number( this._value ) : this._value;
64
+ }
65
+
35
66
  bcmp( other ) {
36
- const rhs = BigNum._coerce( other )._value;
37
- return this._value < rhs ? -1 : ( this._value > rhs ? 1 : 0 );
67
+ const rhs = BigNum._coerce( other );
68
+ const lhsBigInt = this._toBigInt();
69
+ const rhsBigInt = rhs._toBigInt();
70
+ if ( lhsBigInt !== null && rhsBigInt !== null ) {
71
+ return lhsBigInt < rhsBigInt ? -1 : ( lhsBigInt > rhsBigInt ? 1 : 0 );
72
+ }
73
+ return this._toNumber() < rhs._toNumber()
74
+ ? -1
75
+ : ( this._toNumber() > rhs._toNumber() ? 1 : 0 );
38
76
  }
39
77
 
40
78
  beq( other ) { return this.bcmp( other ) === 0; }
@@ -44,45 +82,134 @@ class BigNum {
44
82
  bgt( other ) { return this.bcmp( other ) > 0; }
45
83
  bge( other ) { return this.bcmp( other ) >= 0; }
46
84
 
47
- get babs() { return new BigNum( Math.abs( this._value ), String( Math.abs( this._value ) ), this._isInt ); }
48
- get bneg() { return new BigNum( -this._value, String( -this._value ), this._isInt ); }
49
- get binv() { return new BigNum( 1 / this._value ); }
50
- get bsin() { return new BigNum( Math.sin( this._value ), String( Math.sin( this._value ) ), false ); }
51
- get bcos() { return new BigNum( Math.cos( this._value ) ); }
52
- get btan() { return new BigNum( Math.tan( this._value ), String( Math.tan( this._value ) ), false ); }
53
- get bsqrt() { return new BigNum( Math.sqrt( this._value ) ); }
54
- get bround() { return new BigNum( Math.round( this._value ) ); }
55
- get bfloor() { return new BigNum( Math.floor( this._value ) ); }
56
- get bceil() { return new BigNum( Math.ceil( this._value ) ); }
85
+ babs() {
86
+ const value = this._toBigInt();
87
+ if ( value !== null ) {
88
+ return new BigNum( value < 0n ? -value : value, String( value < 0n ? -value : value ), this._isInt );
89
+ }
90
+ return new BigNum( Math.abs( this._toNumber() ), String( Math.abs( this._toNumber() ) ), this._isInt );
91
+ }
92
+
93
+ bneg() {
94
+ const value = this._toBigInt();
95
+ if ( value !== null ) {
96
+ return new BigNum( -value, String( -value ), this._isInt );
97
+ }
98
+ return new BigNum( -this._toNumber(), String( -this._toNumber() ), this._isInt );
99
+ }
100
+
101
+ binv() { return new BigNum( 1 / this._toNumber() ); }
102
+ bsin() { return new BigNum( Math.sin( this._toNumber() ), String( Math.sin( this._toNumber() ) ), false ); }
103
+ bcos() { return new BigNum( Math.cos( this._toNumber() ) ); }
104
+ btan() { return new BigNum( Math.tan( this._toNumber() ), String( Math.tan( this._toNumber() ) ), false ); }
105
+ bsqrt() { return new BigNum( Math.sqrt( this._toNumber() ) ); }
106
+ bround() { return new BigNum( Math.round( this._toNumber() ) ); }
107
+ bfloor() { return new BigNum( Math.floor( this._toNumber() ) ); }
108
+ bceil() { return new BigNum( Math.ceil( this._toNumber() ) ); }
109
+
110
+ badd( other ) {
111
+ const rhs = BigNum._coerce( other );
112
+ const lhsInt = this._toBigInt();
113
+ const rhsInt = rhs._toBigInt();
114
+ if ( lhsInt !== null && rhsInt !== null ) {
115
+ return new BigNum( lhsInt + rhsInt, String( lhsInt + rhsInt ), false );
116
+ }
117
+ return new BigNum( this._toNumber() + rhs._toNumber(), String( this._toNumber() + rhs._toNumber() ), false );
118
+ }
119
+
120
+ bsub( other ) {
121
+ const rhs = BigNum._coerce( other );
122
+ const lhsInt = this._toBigInt();
123
+ const rhsInt = rhs._toBigInt();
124
+ if ( lhsInt !== null && rhsInt !== null ) {
125
+ return new BigNum( lhsInt - rhsInt, String( lhsInt - rhsInt ), false );
126
+ }
127
+ return new BigNum( this._toNumber() - rhs._toNumber(), String( this._toNumber() - rhs._toNumber() ), false );
128
+ }
129
+
130
+ bmul( other ) {
131
+ const rhs = BigNum._coerce( other );
132
+ const lhsInt = this._toBigInt();
133
+ const rhsInt = rhs._toBigInt();
134
+ if ( lhsInt !== null && rhsInt !== null ) {
135
+ return new BigNum( lhsInt * rhsInt, String( lhsInt * rhsInt ), false );
136
+ }
137
+ return new BigNum( this._toNumber() * rhs._toNumber(), String( this._toNumber() * rhs._toNumber() ), false );
138
+ }
57
139
 
58
- badd( other ) { return new BigNum( this._value + BigNum._coerce( other )._value, String( this._value + BigNum._coerce( other )._value ), false ); }
59
- bsub( other ) { return new BigNum( this._value - BigNum._coerce( other )._value, String( this._value - BigNum._coerce( other )._value ), false ); }
60
- bmul( other ) { return new BigNum( this._value * BigNum._coerce( other )._value, String( this._value * BigNum._coerce( other )._value ), false ); }
61
- bdiv( other ) { return new BigNum( this._value / BigNum._coerce( other )._value ); }
62
- bmod( other ) { return new BigNum( this._value % BigNum._coerce( other )._value ); }
63
- bpow( other ) { return new BigNum( this._value ** BigNum._coerce( other )._value ); }
140
+ bdiv( other ) {
141
+ const rhs = BigNum._coerce( other );
142
+ return new BigNum( this._toNumber() / rhs._toNumber() );
143
+ }
144
+
145
+ bmod( other ) {
146
+ const rhs = BigNum._coerce( other );
147
+ const lhsInt = this._toBigInt();
148
+ const rhsInt = rhs._toBigInt();
149
+ if ( lhsInt !== null && rhsInt !== null && rhsInt !== 0n ) {
150
+ return new BigNum( lhsInt % rhsInt, String( lhsInt % rhsInt ), true );
151
+ }
152
+ return new BigNum( this._toNumber() % rhs._toNumber() );
153
+ }
154
+ bpow( other ) {
155
+ const rhs = BigNum._coerce( other );
156
+ const lhsInt = this._toBigInt();
157
+ const rhsInt = rhs._toBigInt();
158
+ if ( lhsInt !== null && rhsInt !== null && rhsInt >= 0n ) {
159
+ let base = lhsInt;
160
+ let power = rhsInt;
161
+ let result = 1n;
162
+ while ( power > 0n ) {
163
+ if ( power & 1n ) {
164
+ result *= base;
165
+ }
166
+ power >>= 1n;
167
+ if ( power > 0n ) {
168
+ base *= base;
169
+ }
170
+ }
171
+ return new BigNum( result, String( result ), true );
172
+ }
173
+ return new BigNum( this._toNumber() ** rhs._toNumber() );
174
+ }
175
+
176
+ to_hex() {
177
+ const value = this._toBigInt();
178
+ if ( value === null ) {
179
+ return `0x${Math.trunc( this._toNumber() ).toString( 16 )}`;
180
+ }
181
+ if ( value === 0n ) {
182
+ return '0x0';
183
+ }
184
+ const absolute = value < 0n ? -value : value;
185
+ return value < 0n ? `-0x${ absolute.toString( 16 ) }` : `0x${ absolute.toString( 16 ) }`;
186
+ }
187
+
188
+ to_dec() {
189
+ return this._textify();
190
+ }
64
191
 
65
- get to_hex() {
66
- return `0x${Math.trunc( this._value ).toString( 16 )}`;
192
+ to_String() {
193
+ return this.to_dec();
67
194
  }
68
195
 
69
- get to_dec() {
70
- return this._isInt ? Math.trunc( this._value ) : this._textify();
196
+ toString() {
197
+ return String( this.to_String() );
71
198
  }
72
199
 
73
- get to_String() {
74
- return this.to_dec;
200
+ to_Number() {
201
+ return this._toNumber();
75
202
  }
76
203
 
77
- get to_Number() {
78
- return this._value;
204
+ static _isIntegerText( text ) {
205
+ return /^[+-]?(?:\d+)$/u.test( text );
79
206
  }
80
207
 
81
208
  _textify() {
82
209
  if ( this._text != null && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/u.test( this._text ) ) {
83
210
  return this._text.replace( /\.0+$/u, '' ).replace( /(\.\d*?)0+$/u, '$1' ).replace( /\.$/u, '' );
84
211
  }
85
- return String( this._value );
212
+ return this._toBigInt() !== null ? this._toBigInt().toString() : String( this._value );
86
213
  }
87
214
  }
88
215
 
@@ -10,7 +10,7 @@ function normalizeNumberArgs( values ) {
10
10
  }
11
11
 
12
12
  const ZMath = {
13
- pi: PI,
13
+ pi() { return PI; },
14
14
  sin( value ) { return Math.sin( Number( value ?? 0 ) ); },
15
15
  cos( value ) { return Math.cos( Number( value ?? 0 ) ); },
16
16
  tan( value ) { return Math.tan( Number( value ?? 0 ) ); },
@@ -68,37 +68,46 @@ function parse( value ) {
68
68
  return out;
69
69
  }
70
70
 
71
- function _templateVar( values, name ) {
72
- if ( values[name] == null ) {
73
- return '';
74
- }
75
- return encodeURIComponent( _str( values[name] ) );
76
- }
71
+ const { StdUriTemplate } = require( '@std-uritemplate/std-uritemplate' );
77
72
 
78
- function _templateQuery( values, namesText ) {
79
- const names = String( namesText )
80
- .split( /\s*,\s*/u )
81
- .filter( Boolean );
82
- const parts = [];
83
- for ( const name of names ) {
84
- if ( values[name] == null ) {
85
- continue;
73
+ function _templateValue( value ) {
74
+ if ( value == null ) {
75
+ return null;
76
+ }
77
+ if ( Array.isArray( value ) ) {
78
+ return value.map( (item) => _str( item ) );
79
+ }
80
+ if ( typeof value === 'boolean' ) {
81
+ return value ? 'true' : 'false';
82
+ }
83
+ if ( typeof value === 'object' && !( value.bytes instanceof Uint8Array ) ) {
84
+ // Associative values: keys sorted for deterministic expansion.
85
+ const out = {};
86
+ for ( const key of Object.keys( value ).sort() ) {
87
+ out[key] = _str( value[key] );
86
88
  }
87
- const key = encodeURIComponent( name );
88
- const val = encodeURIComponent( _str( values[name] ) );
89
- parts.push( `${key}=${val}` );
89
+ return out;
90
90
  }
91
- return parts.length > 0 ? `?${parts.join( '&' )}` : '';
91
+ return _str( value );
92
92
  }
93
93
 
94
94
  function fill_template( template, values ) {
95
95
  const source = _str( template );
96
- const data = values && typeof values === 'object' && !Array.isArray( values )
97
- ? values
98
- : {};
99
- return source
100
- .replace( /\{\?([^}]+)\}/gu, ( _, names ) => _templateQuery( data, names ) )
101
- .replace( /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/gu, ( _, name ) => _templateVar( data, name ) );
96
+ const data = {};
97
+ if ( values && typeof values === 'object' && !Array.isArray( values ) ) {
98
+ for ( const key of Object.keys( values ) ) {
99
+ const converted = _templateValue( values[key] );
100
+ if ( converted != null ) {
101
+ data[key] = converted;
102
+ }
103
+ }
104
+ }
105
+ try {
106
+ return StdUriTemplate.expand( source, data );
107
+ }
108
+ catch ( err ) {
109
+ throw new Error( `Exception: invalid URL template: ${source}` );
110
+ }
102
111
  }
103
112
 
104
113
  module.exports = {
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ // std/string/encode: character-encoding conversions between String and
4
+ // BinaryString. UTF-16 and UTF-32 encode to big-endian without a BOM (the
5
+ // deterministic canonical form shared by all runtimes); decode honours a
6
+ // leading BOM and otherwise assumes big-endian.
7
+
8
+ const { BinaryString } = require( '../../../lib/runtime-helpers' );
9
+
10
+ function typeName( value ) {
11
+ if ( value == null ) {
12
+ return 'Null';
13
+ }
14
+ if ( typeof value === 'string' || value instanceof String ) {
15
+ return 'String';
16
+ }
17
+ if ( value.bytes instanceof Uint8Array ) {
18
+ return 'BinaryString';
19
+ }
20
+ return value.constructor && value.constructor.name ? value.constructor.name : typeof value;
21
+ }
22
+
23
+ function canonicalEncoding( name ) {
24
+ const upper = String( name == null ? 'UTF-8' : name ).toUpperCase().replace( /\s+/g, '' );
25
+ if ( upper === 'UTF-8' || upper === 'UTF8' ) {
26
+ return 'utf8';
27
+ }
28
+ if ( upper === 'UTF-16' || upper === 'UTF16' || upper === 'UTF-16BE' ) {
29
+ return 'utf16';
30
+ }
31
+ if ( upper === 'UTF-32' || upper === 'UTF32' || upper === 'UTF-32BE' ) {
32
+ return 'utf32';
33
+ }
34
+ if (
35
+ upper === 'ISO-8859-1' || upper === 'ISO8859-1'
36
+ || upper === 'LATIN-1' || upper === 'LATIN1' || upper === 'LATIN'
37
+ ) {
38
+ return 'latin1';
39
+ }
40
+ return null;
41
+ }
42
+
43
+ function codePoints( text ) {
44
+ return Array.from( text, (ch) => ch.codePointAt( 0 ) );
45
+ }
46
+
47
+ function encode( value, encoding ) {
48
+ if ( typeName( value ) !== 'String' ) {
49
+ throw new Error( `TypeException: encode expects String, got ${typeName( value )}` );
50
+ }
51
+ const text = String( value );
52
+ const codec = canonicalEncoding( encoding );
53
+ if ( codec === 'utf8' ) {
54
+ return new BinaryString( new TextEncoder().encode( text ) );
55
+ }
56
+ if ( codec === 'utf16' ) {
57
+ const out = [];
58
+ for ( let i = 0; i < text.length; i++ ) {
59
+ const unit = text.charCodeAt( i );
60
+ out.push( ( unit >> 8 ) & 0xff, unit & 0xff );
61
+ }
62
+ return new BinaryString( out );
63
+ }
64
+ if ( codec === 'utf32' ) {
65
+ const out = [];
66
+ for ( const point of codePoints( text ) ) {
67
+ out.push(
68
+ ( point >>> 24 ) & 0xff,
69
+ ( point >>> 16 ) & 0xff,
70
+ ( point >>> 8 ) & 0xff,
71
+ point & 0xff,
72
+ );
73
+ }
74
+ return new BinaryString( out );
75
+ }
76
+ if ( codec === 'latin1' ) {
77
+ const out = [];
78
+ for ( const point of codePoints( text ) ) {
79
+ if ( point > 0xff ) {
80
+ const hex = point.toString( 16 ).toUpperCase().padStart( 4, '0' );
81
+ throw new Error( `Exception: Character U+${hex} cannot be encoded as ISO-8859-1` );
82
+ }
83
+ out.push( point );
84
+ }
85
+ return new BinaryString( out );
86
+ }
87
+ throw new Error( `Exception: Unsupported encoding: ${encoding}` );
88
+ }
89
+
90
+ function decode( value, encoding ) {
91
+ if ( typeName( value ) !== 'BinaryString' ) {
92
+ throw new Error( `TypeException: decode expects BinaryString, got ${typeName( value )}` );
93
+ }
94
+ let bytes = value.bytes;
95
+ const codec = canonicalEncoding( encoding );
96
+ if ( codec === 'utf8' ) {
97
+ try {
98
+ return new TextDecoder( 'utf-8', { fatal: true } ).decode( bytes );
99
+ }
100
+ catch ( _err ) {
101
+ throw new Error( 'Exception: Invalid UTF-8 in BinaryString' );
102
+ }
103
+ }
104
+ if ( codec === 'utf16' ) {
105
+ let bigEndian = true;
106
+ if ( bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff ) {
107
+ bytes = bytes.subarray( 2 );
108
+ }
109
+ else if ( bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe ) {
110
+ bigEndian = false;
111
+ bytes = bytes.subarray( 2 );
112
+ }
113
+ if ( bytes.length % 2 !== 0 ) {
114
+ throw new Error( 'Exception: UTF-16 input length must be a multiple of 2 bytes' );
115
+ }
116
+ const units = [];
117
+ for ( let i = 0; i < bytes.length; i += 2 ) {
118
+ units.push( bigEndian
119
+ ? ( bytes[i] << 8 ) | bytes[i + 1]
120
+ : ( bytes[i + 1] << 8 ) | bytes[i] );
121
+ }
122
+ let out = '';
123
+ for ( let i = 0; i < units.length; i++ ) {
124
+ const unit = units[i];
125
+ if ( unit >= 0xd800 && unit <= 0xdbff ) {
126
+ const next = units[i + 1];
127
+ if ( next == null || next < 0xdc00 || next > 0xdfff ) {
128
+ throw new Error( 'Exception: Invalid UTF-16 in BinaryString' );
129
+ }
130
+ out += String.fromCharCode( unit, next );
131
+ i++;
132
+ continue;
133
+ }
134
+ if ( unit >= 0xdc00 && unit <= 0xdfff ) {
135
+ throw new Error( 'Exception: Invalid UTF-16 in BinaryString' );
136
+ }
137
+ out += String.fromCharCode( unit );
138
+ }
139
+ return out;
140
+ }
141
+ if ( codec === 'utf32' ) {
142
+ let bigEndian = true;
143
+ if (
144
+ bytes.length >= 4
145
+ && bytes[0] === 0x00 && bytes[1] === 0x00 && bytes[2] === 0xfe && bytes[3] === 0xff
146
+ ) {
147
+ bytes = bytes.subarray( 4 );
148
+ }
149
+ else if (
150
+ bytes.length >= 4
151
+ && bytes[0] === 0xff && bytes[1] === 0xfe && bytes[2] === 0x00 && bytes[3] === 0x00
152
+ ) {
153
+ bigEndian = false;
154
+ bytes = bytes.subarray( 4 );
155
+ }
156
+ if ( bytes.length % 4 !== 0 ) {
157
+ throw new Error( 'Exception: UTF-32 input length must be a multiple of 4 bytes' );
158
+ }
159
+ let out = '';
160
+ for ( let i = 0; i < bytes.length; i += 4 ) {
161
+ const point = bigEndian
162
+ ? ( bytes[i] * 0x1000000 ) + ( bytes[i + 1] << 16 ) + ( bytes[i + 2] << 8 ) + bytes[i + 3]
163
+ : ( bytes[i + 3] * 0x1000000 ) + ( bytes[i + 2] << 16 ) + ( bytes[i + 1] << 8 ) + bytes[i];
164
+ if ( point > 0x10ffff || ( point >= 0xd800 && point <= 0xdfff ) ) {
165
+ throw new Error( 'Exception: Invalid UTF-32 in BinaryString' );
166
+ }
167
+ out += String.fromCodePoint( point );
168
+ }
169
+ return out;
170
+ }
171
+ if ( codec === 'latin1' ) {
172
+ let out = '';
173
+ for ( const byte of bytes ) {
174
+ out += String.fromCharCode( byte );
175
+ }
176
+ return out;
177
+ }
178
+ throw new Error( `Exception: Unsupported encoding: ${encoding}` );
179
+ }
180
+
181
+ module.exports = {
182
+ encode,
183
+ decode,
184
+ ENCODING_UTF8: 'UTF-8',
185
+ ENCODING_UTF16: 'UTF-16',
186
+ ENCODING_UTF32: 'UTF-32',
187
+ ENCODING_LATIN: 'ISO-8859-1',
188
+ };
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { BinaryString } = require( '../../lib/runtime-helpers' );
4
+
3
5
  function toStringValue( value ) {
4
6
  if ( value instanceof RegExp ) {
5
7
  return value.source;
@@ -272,6 +274,23 @@ function camel( text ) {
272
274
  return ws.map( (w, i) => ( i === 0 ? w : `${w[0].toUpperCase()}${w.slice( 1 )}` ) ).join( '' );
273
275
  }
274
276
 
277
+ function to_binary( value ) {
278
+ if ( typeof value !== 'string' && !( value instanceof String ) ) {
279
+ const type = value == null
280
+ ? 'Null'
281
+ : ( value && value.bytes instanceof Uint8Array ) ? 'BinaryString' : typeof value;
282
+ throw new Error( `TypeException: to_binary expects String, got ${type}` );
283
+ }
284
+ return new BinaryString( new TextEncoder().encode( String( value ) ) );
285
+ }
286
+
287
+ function to_string( value ) {
288
+ if ( !( value && value.bytes instanceof Uint8Array ) ) {
289
+ throw new Error( 'TypeException: to_string expects BinaryString' );
290
+ }
291
+ return new TextDecoder( 'utf-8', { fatal: true } ).decode( value.bytes );
292
+ }
293
+
275
294
  module.exports = {
276
295
  camel,
277
296
  chomp,
@@ -295,5 +314,7 @@ module.exports = {
295
314
  sprint,
296
315
  substr,
297
316
  title,
317
+ to_binary,
318
+ to_string,
298
319
  trim,
299
320
  };
@@ -15,6 +15,7 @@ const MONTH_BY_NAME = new Map( [
15
15
  [ 'nov', 11 ], [ 'november', 11 ],
16
16
  [ 'dec', 12 ], [ 'december', 12 ],
17
17
  ] );
18
+ const DAY_ABBR = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ];
18
19
  const WEEKDAY_BY_NAME = new Set( [
19
20
  'sun', 'sunday',
20
21
  'mon', 'monday',
@@ -63,14 +64,72 @@ function offsetSeconds( zone ) {
63
64
  return m[1] === '-' ? -seconds : seconds;
64
65
  }
65
66
 
66
- function pad( value, width = 2 ) {
67
- return String( value ).padStart( width, '0' );
67
+ function pad( value, width = 2, fill = '0' ) {
68
+ return String( value ).padStart( width, fill );
68
69
  }
69
70
 
70
71
  function stripTrailingPeriod( text ) {
71
72
  return String( text ).replace( /\.+$/u, '' ).toLowerCase();
72
73
  }
73
74
 
75
+ function weekdayIndex( parts ) {
76
+ const date = new Date(
77
+ parts.year,
78
+ parts.month - 1,
79
+ parts.day
80
+ );
81
+ return ( ( date.getDay() + 6 ) % 7 ) + 1;
82
+ }
83
+
84
+ function weekParts( parts ) {
85
+ const date = new Date(
86
+ parts.year,
87
+ parts.month - 1,
88
+ parts.day
89
+ );
90
+ const weekDay = date.getDay() || 7;
91
+ const shifted = new Date( date );
92
+ shifted.setDate( date.getDate() + 4 - weekDay );
93
+ const weekYear = shifted.getFullYear();
94
+ const yearStart = new Date( weekYear, 0, 1 );
95
+ return {
96
+ year: weekYear,
97
+ week: Math.ceil( ( shifted - yearStart ) / 86400000 / 7 + 1 ),
98
+ };
99
+ }
100
+
101
+ function dayOfMonthForDisplay( day ) {
102
+ return String( day ).padStart( 2, ' ' );
103
+ }
104
+
105
+ function dayOfYear( parts ) {
106
+ const start = new Date( parts.year, 0, 1 );
107
+ const now = new Date( parts.year, parts.month - 1, parts.day );
108
+ return ( now - start ) / 86400000;
109
+ }
110
+
111
+ function julianDay( parts ) {
112
+ let year = parts.year;
113
+ let month = parts.month;
114
+ const day = parts.day;
115
+ if ( month <= 2 ) {
116
+ year -= 1;
117
+ month += 12;
118
+ }
119
+ const a = Math.floor( ( 14 - month ) / 12 );
120
+ const y = year + 4800 - a;
121
+ const m = month + 12 * a - 3;
122
+ const jd = day
123
+ + Math.floor( ( 153 * m + 2 ) / 5 )
124
+ + 365 * y
125
+ + Math.floor( y / 4 )
126
+ - Math.floor( y / 100 )
127
+ + Math.floor( y / 400 )
128
+ - 32045;
129
+ const fraction = ( parts.hour * 3600 + parts.minute * 60 + parts.second ) / 86400;
130
+ return jd + fraction - 0.5;
131
+ }
132
+
74
133
  function isLeapYear( year ) {
75
134
  if ( year % 4 !== 0 ) return false;
76
135
  if ( year % 100 !== 0 ) return true;
@@ -435,6 +494,39 @@ class Time {
435
494
  mon() { return this._parts().month; }
436
495
  month() { return this.mon(); }
437
496
  year() { return this._parts().year; }
497
+ yy() { return pad( this._parts().year % 100, 2 ); }
498
+ day_of_week() { return weekdayIndex( this._parts() ); }
499
+ day() { return DAY_ABBR[ this.day_of_week() - 1 ]; }
500
+ day_of_year() { return dayOfYear( this._parts() ); }
501
+ month_last_day() { return daysInMonth( this._parts().year, this._parts().month ); }
502
+ hms( separator = ':' ) {
503
+ const p = this._parts();
504
+ return `${pad( p.hour )}${String( separator )}${pad( p.minute )}${String( separator )}${pad( p.second )}`;
505
+ }
506
+ ymd( separator = '-' ) {
507
+ const p = this._parts();
508
+ const sep = String( separator );
509
+ return `${pad( p.year, 4 )}${sep}${pad( p.month )}${sep}${pad( p.day )}`;
510
+ }
511
+ mdy( separator = '-' ) {
512
+ const p = this._parts();
513
+ const sep = String( separator );
514
+ return `${pad( p.month )}${sep}${pad( p.day )}${sep}${pad( p.year, 4 )}`;
515
+ }
516
+ dmy( separator = '-' ) {
517
+ const p = this._parts();
518
+ const sep = String( separator );
519
+ return `${pad( p.day )}${sep}${pad( p.month )}${sep}${pad( p.year, 4 )}`;
520
+ }
521
+ cdate() {
522
+ const p = this._parts();
523
+ return `${DAY_ABBR[ weekdayIndex( p ) - 1 ]} ${MONTHS[p.month - 1]} ${dayOfMonthForDisplay( p.day )} ${pad( p.hour )}:${pad( p.minute )}:${pad( p.second )} ${p.year}`;
524
+ }
525
+ tzoffset() { return offsetForEpoch( this._epoch, this._timezone ); }
526
+ is_leap_year() { return isLeapYear( this._parts().year ); }
527
+ week() { return weekParts( this._parts() ).week; }
528
+ week_year() { return weekParts( this._parts() ).year; }
529
+ julian_day() { return julianDay( this._parts() ); }
438
530
  add_seconds( n ) { return this._clone( this._epoch + Number( n ) ); }
439
531
  add_minutes( n ) { return this.add_seconds( Number( n ) * 60 ); }
440
532
  add_hours( n ) { return this.add_seconds( Number( n ) * 3600 ); }
@@ -509,8 +601,7 @@ class Time {
509
601
  }
510
602
  to_rfc5322( options = {} ) {
511
603
  const p = this._parts();
512
- const d = new Date( Date.UTC( p.year, p.month - 1, p.day ) );
513
- const weekday = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ][ d.getUTCDay() ];
604
+ const weekday = DAY_ABBR[ weekdayIndex( p ) - 1 ];
514
605
  const core = `${pad( p.day )} ${MONTHS[p.month - 1]} ${pad( p.year, 4 )} ${pad( p.hour )}:${pad( p.minute )}:${pad( p.second )} ${formatOffset( offsetForEpoch( this._epoch, this._timezone ), false )}`;
515
606
  return options.include_weekday === false ? core : `${weekday}, ${core}`;
516
607
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuzu-js",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "JavaScript runtime, compiler, and browser bundle for ZuzuScript.",
5
5
  "main": "lib/zuzu.js",
6
6
  "bin": {
@@ -48,6 +48,7 @@
48
48
  "node": ">=16"
49
49
  },
50
50
  "dependencies": {
51
+ "@std-uritemplate/std-uritemplate": "^2.0.10",
51
52
  "@xmldom/xmldom": "^0.9.10",
52
53
  "adm-zip": "^0.5.17",
53
54
  "better-sqlite3": "^11.10.0",