chengkun
2025-09-09 774d962b76d63366ed26c395e0a33cdbec309242
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php
 
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
 
class Duration extends DateTimeWizard
{
    public const DAYS_DURATION = 'd';
 
    /**
     * Hours as a duration (can exceed 24), e.g. 29.
     */
    public const HOURS_DURATION = '[h]';
 
    /**
     * Hours without a leading zero, e.g. 9.
     */
    public const HOURS_SHORT = 'h';
 
    /**
     * Hours with a leading zero, e.g. 09.
     */
    public const HOURS_LONG = 'hh';
 
    /**
     * Minutes as a duration (can exceed 60), e.g. 109.
     */
    public const MINUTES_DURATION = '[m]';
 
    /**
     * Minutes without a leading zero, e.g. 5.
     */
    public const MINUTES_SHORT = 'm';
 
    /**
     * Minutes with a leading zero, e.g. 05.
     */
    public const MINUTES_LONG = 'mm';
 
    /**
     * Seconds as a duration (can exceed 60), e.g. 129.
     */
    public const SECONDS_DURATION = '[s]';
 
    /**
     * Seconds without a leading zero, e.g. 2.
     */
    public const SECONDS_SHORT = 's';
 
    /**
     * Seconds with a leading zero, e.g. 02.
     */
    public const SECONDS_LONG = 'ss';
 
    protected const DURATION_BLOCKS = [
        self::DAYS_DURATION,
        self::HOURS_DURATION,
        self::HOURS_LONG,
        self::HOURS_SHORT,
        self::MINUTES_DURATION,
        self::MINUTES_LONG,
        self::MINUTES_SHORT,
        self::SECONDS_DURATION,
        self::SECONDS_LONG,
        self::SECONDS_SHORT,
    ];
 
    protected const DURATION_MASKS = [
        self::DAYS_DURATION => self::DAYS_DURATION,
        self::HOURS_DURATION => self::HOURS_SHORT,
        self::MINUTES_DURATION => self::MINUTES_LONG,
        self::SECONDS_DURATION => self::SECONDS_LONG,
    ];
 
    protected const DURATION_DEFAULTS = [
        self::HOURS_LONG => self::HOURS_DURATION,
        self::HOURS_SHORT => self::HOURS_DURATION,
        self::MINUTES_LONG => self::MINUTES_DURATION,
        self::MINUTES_SHORT => self::MINUTES_DURATION,
        self::SECONDS_LONG => self::SECONDS_DURATION,
        self::SECONDS_SHORT => self::SECONDS_DURATION,
    ];
 
    public const SEPARATOR_COLON = ':';
    public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
    public const SEPARATOR_SPACE = ' ';
 
    public const DURATION_DEFAULT = [
        self::HOURS_DURATION,
        self::MINUTES_LONG,
        self::SECONDS_LONG,
    ];
 
    /**
     * @var string[]
     */
    protected array $separators;
 
    /**
     * @var string[]
     */
    protected array $formatBlocks;
 
    protected bool $durationIsSet = false;
 
    /**
     * @param null|string|string[] $separators
     *        If you want to use the same separator for all format blocks, then it can be passed as a string literal;
     *           if you wish to use different separators, then they should be passed as an array.
     *        If you want to use only a single format block, then pass a null as the separator argument
     */
    public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
    {
        $separators ??= self::SEPARATOR_COLON;
        $formatBlocks = (count($formatBlocks) === 0) ? self::DURATION_DEFAULT : $formatBlocks;
 
        $this->separators = $this->padSeparatorArray(
            is_array($separators) ? $separators : [$separators],
            count($formatBlocks) - 1
        );
        $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
 
        if ($this->durationIsSet === false) {
            // We need at least one duration mask, so if none has been set we change the first mask element
            //    to a duration.
            $this->formatBlocks[0] = self::DURATION_DEFAULTS[mb_strtolower($this->formatBlocks[0])];
        }
    }
 
    private function mapFormatBlocks(string $value): string
    {
        // Any duration masking codes are returned as lower case values
        if (in_array(mb_strtolower($value), self::DURATION_BLOCKS, true)) {
            if (array_key_exists(mb_strtolower($value), self::DURATION_MASKS)) {
                if ($this->durationIsSet) {
                    // We should only have a single duration mask, the first defined in the mask set,
                    //    so convert any additional duration masks to standard time masks.
                    $value = self::DURATION_MASKS[mb_strtolower($value)];
                }
                $this->durationIsSet = true;
            }
 
            return mb_strtolower($value);
        }
 
        // Wrap any string literals in quotes, so that they're clearly defined as string literals
        return $this->wrapLiteral($value);
    }
 
    public function format(): string
    {
        return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
    }
}