datetime-lube 1.0.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.
package/src/add.js ADDED
@@ -0,0 +1,148 @@
1
+ import { last_day } from "./calendar.js"
2
+ import time_of from "./time_of.js"
3
+ import { check_zone, date_of, fields_of } from "./zone.js"
4
+ /** @type {Map<string, (number | string)[]>} */
5
+ const durations = new Map()
6
+ const duration_regex = /([+-]?\d+)(sss|[YMWDHms])/g
7
+ /** @type {Record<string, number | undefined>} */
8
+ const unit_ms = {
9
+ H: 3600000,
10
+ m: 60000,
11
+ s: 1000,
12
+ sss: 1
13
+ }
14
+ /**
15
+ * @param {Date} date
16
+ * @param {number} months
17
+ * @param {string=} zone
18
+ * @returns {Date}
19
+ */
20
+ function add_months(date, months, zone) {
21
+ if (zone === undefined) {
22
+ const month = date.getMonth() + months
23
+ const year = date.getFullYear() + Math.floor(month / 12)
24
+ const rest = (month % 12 + 12) % 12
25
+ const result = new Date(date.getTime())
26
+ result.setFullYear(
27
+ year,
28
+ rest,
29
+ Math.min(
30
+ date.getDate(),
31
+ last_day(year, rest)
32
+ )
33
+ )
34
+ return result
35
+ }
36
+ const fields = fields_of(date, zone)
37
+ const month = /** @type {number} */(fields[1])/**/ + months
38
+ const year = /** @type {number} */(fields[0])/**/ + Math.floor(month / 12)
39
+ const rest = (month % 12 + 12) % 12
40
+ fields[0] = year
41
+ fields[1] = rest
42
+ fields[2] = Math.min(
43
+ /** @type {number} */(fields[2])/**/,
44
+ last_day(year, rest)
45
+ )
46
+ return date_of(fields, zone)
47
+ }
48
+ /**
49
+ * @param {string} sum
50
+ * @returns {(number | string)[]}
51
+ */
52
+ function compile(sum) {
53
+ let steps = durations.get(sum)
54
+ if (steps) return steps
55
+ steps = []
56
+ if (!/\S/.test(sum) || /\S/.test(
57
+ sum.replace(duration_regex, "")
58
+ )) throw new RangeError(
59
+ `add: Invalid duration "${String(sum)}"`
60
+ )
61
+ for (const [ , value = "", unit = "" ] of sum.matchAll(duration_regex)) {
62
+ if (!+value) continue
63
+ if (unit == "W") steps.push("D", +value * 7)
64
+ else steps.push(unit, +value)
65
+ }
66
+ if (durations.size >= 1000) durations.clear()
67
+ durations.set(sum, steps)
68
+ return steps
69
+ }
70
+ /**
71
+ * Calculates and adds the specified time duration to the provided date and returns a new Date.
72
+ * Years and months are clamped to the end of the month, e.g. Jan 31 + 1M is Feb 28 or 29.
73
+ * Days follow the clock, so a day over a daylight saving change is 23 or 25 hours,
74
+ * while hours, minutes, seconds and milliseconds are elapsed time.
75
+ * A part of zero adds nothing; any other part of years, months, weeks or days lands on a clock time,
76
+ * which moves forward out of a daylight saving gap and takes the first occurrence of an hour that repeats.
77
+ * @param {Date} date
78
+ * @param {string} sum
79
+ * ```
80
+ * e.g. "1Y2M 3D"
81
+ * ```
82
+ *
83
+ * Supported time units:
84
+ * - "Y": Years
85
+ * - "M": Months
86
+ * - "W": Weeks
87
+ * - "D": Days
88
+ * - "H": Hours
89
+ * - "m": Minutes
90
+ * - "s": Seconds
91
+ * - "sss": Milliseconds
92
+ * @param {import("../public.js").TimeZone=} zone
93
+ * @returns {Date}
94
+ * @throws
95
+ * ```
96
+ * RangeError(`add: Invalid duration "${sum}"`) // sum is empty, or a part of it is not a number followed by a unit
97
+ * RangeError(`add: Invalid time zone "${zone}"`) // Intl.DateTimeFormat rejects the zone
98
+ * TypeError(`add: Expected a Date, got ${typeof date}`)
99
+ * ```
100
+ * @example add(new Date, "-1D") //=> Date { yesterday }
101
+ */
102
+ export default function(date, sum, zone) {
103
+ const time = time_of("add", date)
104
+ const steps = compile(sum)
105
+ check_zone("add", zone)
106
+ const result = new Date(time)
107
+ if (isNaN(time)) return result
108
+ if (zone === undefined) {
109
+ for (let i = 0; i < steps.length; i += 2) {
110
+ const unit = /** @type {string} */(steps[i])/**/
111
+ const value = /** @type {number} */(steps[i + 1])/**/
112
+ const ms = unit_ms[unit]
113
+ if (ms) result.setTime(result.getTime() + value * ms)
114
+ else if (unit == "D") result.setDate(result.getDate() + value)
115
+ else result.setTime(
116
+ add_months(
117
+ result,
118
+ unit == "Y"
119
+ ? value * 12
120
+ : value
121
+ ).getTime()
122
+ )
123
+ }
124
+ return result
125
+ }
126
+ let shifted = result
127
+ for (let i = 0; i < steps.length; i += 2) {
128
+ const unit = /** @type {string} */(steps[i])/**/
129
+ const value = /** @type {number} */(steps[i + 1])/**/
130
+ const ms = unit_ms[unit]
131
+ if (ms) {
132
+ shifted = new Date(shifted.getTime() + value * ms)
133
+ } else if (unit == "D") {
134
+ const fields = fields_of(shifted, zone)
135
+ fields[2] = /** @type {number} */(fields[2])/**/ + value
136
+ shifted = date_of(fields, zone)
137
+ } else {
138
+ shifted = add_months(
139
+ shifted,
140
+ unit == "Y"
141
+ ? value * 12
142
+ : value,
143
+ zone
144
+ )
145
+ }
146
+ }
147
+ return shifted
148
+ }
@@ -0,0 +1,88 @@
1
+ const month_days = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
2
+ /**
3
+ * @param {number} year
4
+ * @param {number} month
5
+ * @param {number} day
6
+ * @returns {number}
7
+ */
8
+ function days_of(year, month, day) {
9
+ if (month < 0 || month > 11) {
10
+ year += Math.floor(month / 12)
11
+ month = (month % 12 + 12) % 12
12
+ }
13
+ if (month < 2) year--
14
+ const era = Math.floor(year / 400)
15
+ const yoe = year - era * 400
16
+ return era * 146097
17
+ + yoe * 365
18
+ + Math.floor(yoe / 4)
19
+ - Math.floor(yoe / 100)
20
+ + Math.floor(
21
+ (153 * (month < 2 ? month + 10 : month - 2) + 2) / 5
22
+ )
23
+ + day
24
+ - 719469
25
+ }
26
+ /**
27
+ * @param {number} wall
28
+ * @returns {number[]}
29
+ */
30
+ function fields_of_wall(wall) {
31
+ const days = Math.floor(wall / 86400000)
32
+ let rest = wall - days * 86400000
33
+ const hour = Math.floor(rest / 3600000)
34
+ rest -= hour * 3600000
35
+ const minute = Math.floor(rest / 60000)
36
+ rest -= minute * 60000
37
+ const second = Math.floor(rest / 1000)
38
+ const z = days + 719468
39
+ const era = Math.floor(z / 146097)
40
+ const doe = z - era * 146097
41
+ const yoe = Math.floor(
42
+ (doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365
43
+ )
44
+ const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100))
45
+ const mp = Math.floor((5 * doy + 2) / 153)
46
+ const month = mp < 10 ? mp + 2 : mp - 10
47
+ return [
48
+ yoe + era * 400 + (month < 2 ? 1 : 0),
49
+ month,
50
+ doy - Math.floor((153 * mp + 2) / 5) + 1,
51
+ hour,
52
+ minute,
53
+ second,
54
+ rest - second * 1000,
55
+ (days % 7 + 11) % 7
56
+ ]
57
+ }
58
+ /**
59
+ * @param {number} year
60
+ * @param {number} month
61
+ * @returns {number}
62
+ */
63
+ function last_day(year, month) {
64
+ return month == 1 && !(year % 4) && (year % 100 || !(year % 400))
65
+ ? 29
66
+ : /** @type {number} */(month_days[month])/**/
67
+ }
68
+ /**
69
+ * @param {number[]} fields
70
+ * @returns {number}
71
+ */
72
+ function wall_of_fields(fields) {
73
+ return days_of(
74
+ /** @type {number} */(fields[0])/**/,
75
+ /** @type {number} */(fields[1])/**/,
76
+ /** @type {number} */(fields[2])/**/
77
+ ) * 86400000
78
+ + /** @type {number} */(fields[3])/**/ * 3600000
79
+ + /** @type {number} */(fields[4])/**/ * 60000
80
+ + /** @type {number} */(fields[5])/**/ * 1000
81
+ + /** @type {number} */(fields[6])/**/
82
+ }
83
+ export {
84
+ days_of,
85
+ fields_of_wall,
86
+ last_day,
87
+ wall_of_fields
88
+ }
@@ -0,0 +1,106 @@
1
+ import time_of from "./time_of.js"
2
+ import { check_zone, fields_of } from "./zone.js"
3
+ /** @type {Map<string, (string | number[])[]>} */
4
+ const formats = new Map()
5
+ const token_regex = /DD|HH|MM|YYYY|mm|sss?/g
6
+ const year_token = [ 0, 4, 0 ]
7
+ /** @type {Record<string, number[]>} */
8
+ const tokens = {
9
+ DD: [ 2, 2, 0 ],
10
+ HH: [ 3, 2, 0 ],
11
+ MM: [ 1, 2, 1 ],
12
+ YYYY: year_token,
13
+ mm: [ 4, 2, 0 ],
14
+ ss: [ 5, 2, 0 ],
15
+ sss: [ 6, 3, 0 ]
16
+ }
17
+ /**
18
+ * @param {string} format
19
+ * @returns {(string | number[])[]}
20
+ */
21
+ function compile(format) {
22
+ let parts = formats.get(format)
23
+ if (parts) return parts
24
+ parts = []
25
+ let last = 0
26
+ for (const { 0: token, index = 0 } of format.matchAll(token_regex)) {
27
+ if (index > last) parts.push(format.slice(last, index))
28
+ parts.push(
29
+ /** @type {number[]} */(tokens[token])/**/
30
+ )
31
+ last = index + token.length
32
+ }
33
+ if (last < format.length) parts.push(format.slice(last))
34
+ if (formats.size >= 1000) formats.clear()
35
+ formats.set(format, parts)
36
+ return parts
37
+ }
38
+ /**
39
+ * @param {number} value
40
+ * @param {number} length
41
+ * @returns {string}
42
+ */
43
+ function pad(value, length) {
44
+ return String(value).padStart(length, "0")
45
+ }
46
+ /**
47
+ * @param {number} year
48
+ * @returns {string}
49
+ */
50
+ function year_text(year) {
51
+ return year >= 0 && year <= 9999
52
+ ? String(year).padStart(4, "0")
53
+ : (year < 0 ? "-" : "+") + String(Math.abs(year)).padStart(6, "0")
54
+ }
55
+ /**
56
+ * Converts a Date object to a formatted string representation.
57
+ * Letters that are not a time unit are kept as they are, so `"HHhmm"` gives `"11h22"`.
58
+ * A year before 0 or after 9999 is printed as an expanded year, such as `"+012345"`.
59
+ * @param {Date} date
60
+ * @param {string=} format
61
+ * ```js
62
+ * = "YYYY-MM-DDTHH:mm:ss.sss"
63
+ * ```
64
+ *
65
+ * Supported time units:
66
+ * - "YYYY": Years
67
+ * - "MM": Months
68
+ * - "DD": Days
69
+ * - "HH": Hours
70
+ * - "mm": Minutes
71
+ * - "ss": Seconds
72
+ * - "sss": Milliseconds
73
+ * @param {import("../public.js").TimeZone=} zone
74
+ * @returns {string}
75
+ * @throws
76
+ * ```
77
+ * RangeError(`dateToString: Invalid time zone "${zone}"`) // Intl.DateTimeFormat rejects the zone
78
+ * TypeError(`dateToString: Expected a Date, got ${typeof date}`)
79
+ * ```
80
+ * @example dateToString(new Date, "YYYY-MM-DD HH:mm", "Asia/Seoul") //=> "2024-07-15 21:00"
81
+ */
82
+ export default function(
83
+ date,
84
+ format = "YYYY-MM-DDTHH:mm:ss.sss",
85
+ zone
86
+ ) {
87
+ const time = time_of("dateToString", date)
88
+ check_zone("dateToString", zone)
89
+ if (isNaN(time)) return "Invalid Date"
90
+ const fields = fields_of(date, zone)
91
+ const parts = compile(format)
92
+ let result = ""
93
+ for (const part of parts) {
94
+ result += typeof part == "string"
95
+ ? part
96
+ : part === year_token
97
+ ? year_text(
98
+ /** @type {number} */(fields[0])/**/
99
+ )
100
+ : pad(
101
+ /** @type {number} */(fields[/** @type {number} */(part[0])/**/])/**/ + /** @type {number} */(part[2])/**/,
102
+ /** @type {number} */(part[1])/**/
103
+ )
104
+ }
105
+ return result
106
+ }
package/src/diff.js ADDED
@@ -0,0 +1,143 @@
1
+ import { days_of, last_day, wall_of_fields } from "./calendar.js"
2
+ import time_of from "./time_of.js"
3
+ import { check_zone, date_of, fields_of } from "./zone.js"
4
+ /** @type {Record<string, number | undefined>} */
5
+ const unit_ms = {
6
+ H: 3600000,
7
+ m: 60000,
8
+ s: 1000,
9
+ sss: 1
10
+ }
11
+ const units_of = new Set(
12
+ [ "D", "H", "M", "W", "Y", "m", "s", "sss" ]
13
+ )
14
+ /**
15
+ * @param {number[]} from_fields
16
+ * @param {number} months
17
+ * @param {number} to_wall
18
+ * @param {number} end
19
+ * @param {string=} zone
20
+ * @returns {number}
21
+ */
22
+ function anchor_after(
23
+ from_fields,
24
+ months,
25
+ to_wall,
26
+ end,
27
+ zone
28
+ ) {
29
+ const month = /** @type {number} */(from_fields[1])/**/ + months
30
+ const year = /** @type {number} */(from_fields[0])/**/ + Math.floor(month / 12)
31
+ const index = (month % 12 + 12) % 12
32
+ const fields = [
33
+ year,
34
+ index,
35
+ Math.min(
36
+ /** @type {number} */(from_fields[2])/**/,
37
+ last_day(year, index)
38
+ ),
39
+ /** @type {number} */(from_fields[3])/**/,
40
+ /** @type {number} */(from_fields[4])/**/,
41
+ /** @type {number} */(from_fields[5])/**/,
42
+ /** @type {number} */(from_fields[6])/**/
43
+ ]
44
+ const distance = wall_of_fields(fields) - to_wall
45
+ if (Math.abs(distance) >= 172800000) return distance
46
+ const time = date_of(fields, zone).getTime()
47
+ return isNaN(time)
48
+ ? distance
49
+ : time - end
50
+ }
51
+ /**
52
+ * Counts the whole units from one date to another, negative when the second date is earlier.
53
+ * Years, months, weeks and days follow the clock, so a day over a daylight saving change still
54
+ * counts as one day, while hours, minutes, seconds and milliseconds are elapsed time.
55
+ * @param {Date} from
56
+ * @param {Date} to
57
+ * @param {import("../public.js").Unit} unit
58
+ * ```
59
+ * "Y" | "M" | "W" | "D" | "H" | "m" | "s" | "sss"
60
+ * ```
61
+ * @param {import("../public.js").TimeZone=} zone
62
+ * @returns {number}
63
+ * @throws
64
+ * ```
65
+ * RangeError(`diff: Invalid unit "${unit}"`) // The unit is not one of the listed ones
66
+ * RangeError(`diff: Invalid time zone "${zone}"`) // Intl.DateTimeFormat rejects the zone
67
+ * TypeError(`diff: Expected a Date, got ${typeof from}`) // from or to is not a Date
68
+ * ```
69
+ * @example diff(start, end, "D") //=> 30
70
+ */
71
+ export default function(from, to, unit, zone) {
72
+ const start = time_of("diff", from)
73
+ const end = time_of("diff", to)
74
+ if (!units_of.has(unit)) throw new RangeError(
75
+ `diff: Invalid unit "${String(unit)}"`
76
+ )
77
+ check_zone("diff", zone)
78
+ if (isNaN(start) || isNaN(end)) return NaN
79
+ const ms = unit_ms[unit]
80
+ if (ms) return Math.trunc((end - start) / ms) + 0
81
+ const from_fields = fields_of(from, zone)
82
+ const to_fields = fields_of(to, zone)
83
+ if (unit == "D" || unit == "W") {
84
+ const days = days_of(
85
+ /** @type {number} */(to_fields[0])/**/,
86
+ /** @type {number} */(to_fields[1])/**/,
87
+ /** @type {number} */(to_fields[2])/**/
88
+ ) - days_of(
89
+ /** @type {number} */(from_fields[0])/**/,
90
+ /** @type {number} */(from_fields[1])/**/,
91
+ /** @type {number} */(from_fields[2])/**/
92
+ )
93
+ return unit == "D"
94
+ ? days
95
+ : Math.trunc(days / 7) + 0
96
+ }
97
+ let months = (/** @type {number} */(to_fields[0])/**/ - /** @type {number} */(from_fields[0])/**/) * 12
98
+ + /** @type {number} */(to_fields[1])/**/ - /** @type {number} */(from_fields[1])/**/
99
+ const to_day = /** @type {number} */(to_fields[2])/**/
100
+ const from_day = /** @type {number} */(from_fields[2])/**/
101
+ if (Math.abs(to_day - from_day) > 2 && to_day < 27 && from_day < 27) {
102
+ if (months > 0 && to_day < from_day) months--
103
+ else if (months < 0 && to_day > from_day) months++
104
+ } else {
105
+ const to_wall = wall_of_fields(to_fields)
106
+ if (end >= start) {
107
+ months = Math.max(months, 0)
108
+ while (months && anchor_after(
109
+ from_fields,
110
+ months,
111
+ to_wall,
112
+ end,
113
+ zone
114
+ ) > 0) months--
115
+ if (anchor_after(
116
+ from_fields,
117
+ months + 1,
118
+ to_wall,
119
+ end,
120
+ zone
121
+ ) <= 0) months++
122
+ } else {
123
+ months = Math.min(months, 0)
124
+ while (months && anchor_after(
125
+ from_fields,
126
+ months,
127
+ to_wall,
128
+ end,
129
+ zone
130
+ ) < 0) months++
131
+ if (anchor_after(
132
+ from_fields,
133
+ months - 1,
134
+ to_wall,
135
+ end,
136
+ zone
137
+ ) >= 0) months--
138
+ }
139
+ }
140
+ return unit == "Y"
141
+ ? Math.trunc(months / 12) + 0
142
+ : months
143
+ }
package/src/end_of.js ADDED
@@ -0,0 +1,118 @@
1
+ import {
2
+ days_of,
3
+ fields_of_wall,
4
+ last_day,
5
+ wall_of_fields
6
+ } from "./calendar.js"
7
+ import time_of from "./time_of.js"
8
+ import {
9
+ check_zone,
10
+ end_at,
11
+ instant_of,
12
+ offset_in,
13
+ offset_of,
14
+ unit_end
15
+ } from "./zone.js"
16
+ /** @type {Record<string, number | undefined>} */
17
+ const unit_ms = { H: 3600000, m: 60000, s: 1000 }
18
+ /** @type {Record<string, number>} */
19
+ const units = { D: 3, M: 2, W: 3, Y: 1 }
20
+ const units_of = new Set(
21
+ [ "D", "H", "M", "W", "Y", "m", "s" ]
22
+ )
23
+ /**
24
+ * Returns a new Date at the last millisecond of the given unit, such as 23:59:59.999 of the day.
25
+ * A week ends on Sunday, as in ISO 8601.
26
+ * @param {Date} date
27
+ * @param {Exclude<import("../public.js").Unit, "sss">} unit
28
+ * ```
29
+ * "Y" | "M" | "W" | "D" | "H" | "m" | "s"
30
+ * ```
31
+ * @param {import("../public.js").TimeZone=} zone
32
+ * @returns {Date}
33
+ * @throws
34
+ * ```
35
+ * RangeError(`endOf: Invalid unit "${unit}"`) // The unit is not one of the listed ones
36
+ * RangeError(`endOf: Invalid time zone "${zone}"`) // Intl.DateTimeFormat rejects the zone
37
+ * TypeError(`endOf: Expected a Date, got ${typeof date}`)
38
+ * ```
39
+ * @example endOf(new Date, "M") //=> Date { last millisecond of this month }
40
+ */
41
+ export default function(date, unit, zone) {
42
+ const time = time_of("endOf", date)
43
+ if (!units_of.has(unit)) throw new RangeError(
44
+ `endOf: Invalid unit "${String(unit)}"`
45
+ )
46
+ check_zone("endOf", zone)
47
+ if (isNaN(time)) return new Date(NaN)
48
+ const ms = unit_ms[unit]
49
+ if (ms) {
50
+ if (zone !== undefined) {
51
+ const offset = offset_of(time, zone)
52
+ const rest = ms - ((time + offset) % ms + ms) % ms
53
+ return new Date(
54
+ offset_of(time + rest - 1, zone) == offset
55
+ ? time + rest - 1
56
+ : unit_end(time, ms, zone)
57
+ )
58
+ }
59
+ const rest = ms - (
60
+ ms > 60000
61
+ ? date.getMinutes() * 60000 + date.getSeconds() * 1000 + date.getMilliseconds()
62
+ : ms > 1000
63
+ ? date.getSeconds() * 1000 + date.getMilliseconds()
64
+ : date.getMilliseconds()
65
+ )
66
+ const result = new Date(time + rest - 1)
67
+ if (
68
+ result.getTimezoneOffset() == date.getTimezoneOffset()
69
+ && result.getSeconds() == (ms > 1000 ? 59 : date.getSeconds())
70
+ ) return result
71
+ result.setTime(unit_end(time, ms))
72
+ return result
73
+ }
74
+ const from = /** @type {number} */(units[unit])/**/
75
+ let end = time
76
+ if (zone === undefined) {
77
+ const result = new Date(time)
78
+ result.setHours(
79
+ unit == "D"
80
+ ? 24
81
+ : unit == "W"
82
+ ? 168 - 24 * ((result.getDay() + 6) % 7)
83
+ : unit == "M"
84
+ ? 24 * (last_day(
85
+ result.getFullYear(),
86
+ result.getMonth()
87
+ ) - result.getDate() + 1)
88
+ : 24 * (days_of(result.getFullYear() + 1, 0, 1) - days_of(
89
+ result.getFullYear(),
90
+ result.getMonth(),
91
+ result.getDate()
92
+ )),
93
+ 0,
94
+ 0,
95
+ 0
96
+ )
97
+ end = result.getTime() - 1
98
+ if (end >= time && !(result.getHours() || result.getMinutes() || result.getSeconds())) {
99
+ result.setTime(end)
100
+ return result
101
+ }
102
+ }
103
+ const offset = offset_in(time, zone)
104
+ const fields = fields_of_wall(time + offset)
105
+ if (unit == "W") fields[2] = /** @type {number} */(fields[2])/**/ + 7 - (/** @type {number} */(fields[7])/**/ + 6) % 7
106
+ else fields[from - 1] = /** @type {number} */(fields[from - 1])/**/ + 1
107
+ for (let i = from; i < 7; i++) fields[i] = i == 2 ? 1 : 0
108
+ const wall = wall_of_fields(fields)
109
+ if (zone !== undefined) {
110
+ const next = offset_of(wall - offset, zone)
111
+ if (
112
+ next == offset
113
+ || wall - next > time && offset_of(wall - next, zone) == next
114
+ ) return new Date(wall - next - 1)
115
+ end = instant_of(wall, zone) - 1
116
+ }
117
+ return new Date(end_at(wall, end, time, zone))
118
+ }
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { default as add } from "./add.js"
2
+ export { default as dateToString } from "./date_to_string.js"
3
+ export { default as diff } from "./diff.js"
4
+ export { default as endOf } from "./end_of.js"
5
+ export { default as startOf } from "./start_of.js"
6
+ export { default as stringToDate } from "./string_to_date.js"
7
+ export { default as timeUnit } from "./time_unit.js"
8
+ export { default as timeZoneOffset } from "./time_zone_offset.js"