• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ekino / veggies / 14512275556

17 Apr 2025 09:13AM UTC coverage: 94.169% (-2.3%) from 96.46%
14512275556

push

github

web-flow
Merge pull request #96 from tduyng/2.x

A major rewrite for @ekino/veggies 2.0

576 of 616 branches covered (93.51%)

Branch coverage included in aggregate %.

801 of 847 new or added lines in 17 files covered. (94.57%)

829 of 876 relevant lines covered (94.63%)

67.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.98
/src/utils/time.ts
1
const formatterCache = new Map<string, Intl.DateTimeFormat>()
4✔
2

3
const getFormatter = (locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat => {
4✔
4
    const key = locale + JSON.stringify(options)
80✔
5
    if (formatterCache.has(key)) {
80✔
6
        return formatterCache.get(key) as Intl.DateTimeFormat
64✔
7
    }
64✔
8
    const formatter = new Intl.DateTimeFormat(locale, options)
16✔
9
    formatterCache.set(key, formatter)
16✔
10
    return formatter
16✔
11
}
16✔
12

13
/**
14
 * Adds a relative offset to a given Date.
15
 * @param date - The base date.
16
 * @param opts - Options with properties:
17
 *   - unit: a string such as 'days', 'months', etc.
18
 *   - amount: a number (can be negative)
19
 * @returns A new Date instance with the offset applied.
20
 */
21
export const addTime = (date: Date, { unit, amount }: { unit: string; amount: number }): Date => {
4✔
22
    const newDate = new Date(date.getTime())
40✔
23
    const lowerUnit = unit.toLowerCase()
40✔
24
    switch (lowerUnit) {
40✔
25
        case 'year':
40!
26
        case 'years':
40!
NEW
27
            newDate.setFullYear(newDate.getFullYear() + amount)
×
NEW
28
            break
×
29
        case 'month':
40!
30
        case 'months':
40!
NEW
31
            newDate.setMonth(newDate.getMonth() + amount)
×
NEW
32
            break
×
33
        case 'week':
40!
34
        case 'weeks':
40!
NEW
35
            newDate.setDate(newDate.getDate() + amount * 7)
×
NEW
36
            break
×
37
        case 'day':
40!
38
        case 'days':
40✔
39
            newDate.setDate(newDate.getDate() + amount)
40✔
40
            break
40✔
41
        case 'hour':
40!
42
        case 'hours':
40!
NEW
43
            newDate.setHours(newDate.getHours() + amount)
×
NEW
44
            break
×
45
        case 'minute':
40!
46
        case 'minutes':
40!
NEW
47
            newDate.setMinutes(newDate.getMinutes() + amount)
×
NEW
48
            break
×
49
        case 'second':
40!
50
        case 'seconds':
40!
NEW
51
            newDate.setSeconds(newDate.getSeconds() + amount)
×
NEW
52
            break
×
53
        case 'millisecond':
40!
54
        case 'milliseconds':
40!
NEW
55
            newDate.setMilliseconds(newDate.getMilliseconds() + amount)
×
NEW
56
            break
×
57
        default:
40!
NEW
58
            throw new Error(`Unsupported unit: ${unit}`)
×
59
    }
40✔
60
    return newDate
40✔
61
}
40✔
62

63
/**
64
 * Formats a Date object into a string according to a custom format.
65
 *
66
 * Supported tokens (case-sensitive):
67
 *   - YYYY or yyyy: 4-digit year
68
 *   - YY or yy: 2-digit year
69
 *   - MMMM: full month name (locale-dependent)
70
 *   - MMM: abbreviated month name (locale-dependent)
71
 *   - MM: 2-digit month number
72
 *   - M: month number without leading zero
73
 *   - DD or dd: 2-digit day of month
74
 *   - D or d: day of month without leading zero
75
 *   - HH: 2-digit hour (24-hour clock)
76
 *   - H: hour (24-hour clock) without leading zero
77
 *   - hh: 2-digit hour (12-hour clock)
78
 *   - h: hour (12-hour clock) without leading zero
79
 *   - mm: 2-digit minute
80
 *   - m: minute without leading zero
81
 *   - ss: 2-digit second
82
 *   - s: second without leading zero
83
 *
84
 * Literal text can be included by wrapping it in square brackets, e.g. [Today].
85
 *
86
 * @param date - The Date to format.
87
 * @param fmt - The format string.
88
 * @param locale - A BCP47 locale string.
89
 * @returns The formatted date.
90
 */
91
export const formatTime = (date: Date, fmt: string, locale: string): string => {
4✔
92
    const pad = (n: number, width = 2): string => n.toString().padStart(width, '0')
40✔
93
    const fullMonthFormatter = getFormatter(locale, { month: 'long' })
40✔
94
    const shortMonthFormatter = getFormatter(locale, { month: 'short' })
40✔
95
    const fullMonth = fullMonthFormatter.format(date)
40✔
96
    const shortMonth = shortMonthFormatter.format(date)
40✔
97

98
    const tokenMap: Record<string, string> = {
40✔
99
        yyyy: pad(date.getFullYear(), 4),
40✔
100
        YYYY: pad(date.getFullYear(), 4),
40✔
101
        yy: pad(date.getFullYear() % 100, 2),
40✔
102
        YY: pad(date.getFullYear() % 100, 2),
40✔
103
        MMMM: fullMonth,
40✔
104
        MMM: shortMonth,
40✔
105
        MM: pad(date.getMonth() + 1, 2),
40✔
106
        M: (date.getMonth() + 1).toString(),
40✔
107
        dd: pad(date.getDate(), 2),
40✔
108
        DD: pad(date.getDate(), 2),
40✔
109
        d: date.getDate().toString(),
40✔
110
        D: date.getDate().toString(),
40✔
111
        HH: pad(date.getHours(), 2),
40✔
112
        H: date.getHours().toString(),
40✔
113
        // 12-hour clock tokens:
114
        hh: pad(date.getHours() % 12 || 12, 2),
40✔
115
        h: (date.getHours() % 12 || 12).toString(),
40✔
116
        mm: pad(date.getMinutes(), 2),
40✔
117
        m: date.getMinutes().toString(),
40✔
118
        ss: pad(date.getSeconds(), 2),
40✔
119
        s: date.getSeconds().toString(),
40✔
120
    }
40✔
121
    const regex = /\[([^\]]+)\]|(YYYY|yyyy|YY|yy|MMMM|MMM|MM|M|DD|dd|D|d|HH|H|hh|h|mm|m|ss|s)/g
40✔
122
    return fmt.replace(regex, (match: string, literal: string, token: string): string => {
40✔
123
        if (literal !== undefined) {
152✔
124
            return literal
16✔
125
        }
16✔
126
        return tokenMap[token] !== undefined ? tokenMap[token] : match
152!
127
    })
40✔
128
}
40✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc