6
6
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
7
7
8
8
use chrono:: format:: { Item , StrftimeItems } ;
9
- use chrono:: { DateTime , FixedOffset , Local , Offset , TimeDelta , Utc } ;
9
+ use chrono:: { DateTime , FixedOffset , Local , TimeDelta , Utc } ;
10
10
#[ cfg( windows) ]
11
11
use chrono:: { Datelike , Timelike } ;
12
12
use clap:: { Arg , ArgAction , Command } ;
@@ -136,6 +136,27 @@ impl From<&str> for Rfc3339Format {
136
136
}
137
137
}
138
138
139
+ /// Check if we should use UTC time based on the utc flag and TZ environment variable
140
+ fn should_use_utc ( utc : bool ) -> bool {
141
+ // Either the -u flag is set or the TZ environment variable is empty
142
+ utc || match std:: env:: var ( "TZ" ) {
143
+ Ok ( tz) if tz. is_empty ( ) => true ,
144
+ _ => false ,
145
+ }
146
+ }
147
+
148
+ /// Get the current time, considering the utc flag and TZ environment variable
149
+ fn get_current_time ( utc : bool ) -> DateTime < Local > {
150
+ if should_use_utc ( utc) {
151
+ // When -u flag is used or TZ is empty, we should use UTC time
152
+ // Simply convert UTC to Local without adjusting the time value
153
+ Utc :: now ( ) . into ( )
154
+ } else {
155
+ // Use local time when neither condition is met
156
+ Local :: now ( )
157
+ }
158
+ }
159
+
139
160
#[ uucore:: main]
140
161
#[ allow( clippy:: cognitive_complexity) ]
141
162
pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
@@ -167,7 +188,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
167
188
} ;
168
189
169
190
let date_source = if let Some ( date) = matches. get_one :: < String > ( OPT_DATE ) {
170
- let ref_time = Local :: now ( ) ;
191
+ let ref_time = get_current_time ( matches . get_flag ( OPT_UNIVERSAL ) ) ;
171
192
if let Ok ( new_time) = parse_datetime:: parse_datetime_at_date ( ref_time, date. as_str ( ) ) {
172
193
let duration = new_time. signed_duration_since ( ref_time) ;
173
194
DateSource :: Human ( duration)
@@ -212,11 +233,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
212
233
return set_system_datetime ( date) ;
213
234
} else {
214
235
// Get the current time, either in the local time zone or UTC.
215
- let now: DateTime < FixedOffset > = if settings. utc {
216
- let now = Utc :: now ( ) ;
217
- now. with_timezone ( & now. offset ( ) . fix ( ) )
236
+ let now = get_current_time ( settings. utc ) ;
237
+ // Convert to FixedOffset for consistent timezone handling
238
+ let now_fixed = if should_use_utc ( settings. utc ) {
239
+ // When -u flag is used or TZ is empty, use actual UTC time with zero offset
240
+ let utc_now = Utc :: now ( ) ;
241
+ utc_now. with_timezone ( & FixedOffset :: east_opt ( 0 ) . unwrap ( ) )
218
242
} else {
219
- let now = Local :: now ( ) ;
243
+ // Otherwise use the local timezone's offset
220
244
now. with_timezone ( now. offset ( ) )
221
245
} ;
222
246
@@ -230,7 +254,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
230
254
DateSource :: Human ( relative_time) => {
231
255
// Double check the result is overflow or not of the current_time + relative_time
232
256
// it may cause a panic of chrono::datetime::DateTime add
233
- match now . checked_add_signed ( relative_time) {
257
+ match now_fixed . checked_add_signed ( relative_time) {
234
258
Some ( date) => {
235
259
let iter = std:: iter:: once ( Ok ( date) ) ;
236
260
Box :: new ( iter)
@@ -262,7 +286,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
262
286
Box :: new ( iter)
263
287
}
264
288
DateSource :: Now => {
265
- let iter = std:: iter:: once ( Ok ( now ) ) ;
289
+ let iter = std:: iter:: once ( Ok ( now_fixed ) ) ;
266
290
Box :: new ( iter)
267
291
}
268
292
} ;
@@ -273,7 +297,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
273
297
for date in dates {
274
298
match date {
275
299
Ok ( date) => {
276
- let format_string = custom_time_format ( format_string) ;
300
+ let format_string = if should_use_utc ( settings. utc ) {
301
+ // When -u flag is used or TZ is empty, replace %Z with UTC directly
302
+ format_string. replace ( "%Z" , "UTC" )
303
+ } else {
304
+ custom_time_format ( format_string)
305
+ } ;
306
+
277
307
// Refuse to pass this string to chrono as it is crashing in this crate
278
308
if format_string. contains ( "%#z" ) {
279
309
return Err ( USimpleError :: new (
@@ -290,10 +320,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
290
320
format ! ( "invalid format {}" , format_string. replace( "%f" , "%N" ) ) ,
291
321
) ) ;
292
322
}
293
- let formatted = date
323
+
324
+ // When -u flag is used or TZ is empty, ensure we format using UTC time
325
+ let date_to_format = if should_use_utc ( settings. utc ) {
326
+ // Convert the date to UTC to ensure correct time display
327
+ date. with_timezone ( & Utc )
328
+ . with_timezone ( & FixedOffset :: east_opt ( 0 ) . unwrap ( ) )
329
+ } else {
330
+ date
331
+ } ;
332
+
333
+ let formatted = date_to_format
294
334
. format_with_items ( format_items)
295
335
. to_string ( )
296
336
. replace ( "%f" , "%N" ) ;
337
+
297
338
println ! ( "{formatted}" ) ;
298
339
}
299
340
Err ( ( input, _err) ) => show ! ( USimpleError :: new(
0 commit comments