@@ -18,7 +18,11 @@ use time::macros::{format_description, offset, time};
1818use time:: Duration ;
1919use uucore:: display:: Quotable ;
2020use uucore:: error:: { FromIo , UError , UResult , USimpleError } ;
21+ use uucore:: parse_date;
22+ use uucore:: parse_date_common:: local_dt_to_filetime;
2123use uucore:: parse_relative_time;
24+ use uucore:: parse_timestamp;
25+
2226use uucore:: { format_usage, help_about, help_usage, show} ;
2327
2428const ABOUT : & str = help_about ! ( "touch.md" ) ;
@@ -42,29 +46,6 @@ pub mod options {
4246
4347static ARG_FILES : & str = "files" ;
4448
45- // Convert a date/time to a date with a TZ offset
46- fn to_local ( tm : time:: PrimitiveDateTime ) -> time:: OffsetDateTime {
47- let offset = match time:: OffsetDateTime :: now_local ( ) {
48- Ok ( lo) => lo. offset ( ) ,
49- Err ( e) => {
50- panic ! ( "error: {e}" ) ;
51- }
52- } ;
53- tm. assume_offset ( offset)
54- }
55-
56- // Convert a date/time with a TZ offset into a FileTime
57- fn local_dt_to_filetime ( dt : time:: OffsetDateTime ) -> FileTime {
58- FileTime :: from_unix_time ( dt. unix_timestamp ( ) , dt. nanosecond ( ) )
59- }
60-
61- // Convert a date/time, considering that the input is in UTC time
62- // Used for touch -d 1970-01-01 18:43:33.023456789 for example
63- fn dt_to_filename ( tm : time:: PrimitiveDateTime ) -> FileTime {
64- let dt = tm. assume_offset ( offset ! ( UTC ) ) ;
65- local_dt_to_filetime ( dt)
66- }
67-
6849#[ uucore:: main]
6950pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
7051 let matches = uu_app ( ) . try_get_matches_from ( args) ?;
@@ -108,21 +89,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
10889
10990 ( atime, mtime)
11091 } else {
111- let timestamp = parse_date ( date) ?;
92+ let timestamp = parse_date:: from_str ( date) ?;
11293 ( timestamp, timestamp)
11394 }
11495 }
11596 ( Some ( reference) , None ) => {
11697 stat ( Path :: new ( reference) , !matches. get_flag ( options:: NO_DEREF ) ) ?
11798 }
11899 ( None , Some ( date) ) => {
119- let timestamp = parse_date ( date) ?;
100+ let timestamp = parse_date:: from_str ( date) ?;
120101 ( timestamp, timestamp)
121102 }
122103 ( None , None ) => {
123104 let timestamp =
124105 if let Some ( current) = matches. get_one :: < String > ( options:: sources:: CURRENT ) {
125- parse_timestamp ( current) ?
106+ parse_timestamp:: from_str ( current) ?
126107 } else {
127108 local_dt_to_filetime ( time:: OffsetDateTime :: now_local ( ) . unwrap ( ) )
128109 } ;
@@ -320,192 +301,6 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
320301 ) )
321302}
322303
323- const POSIX_LOCALE_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
324- "[weekday repr:short] [month repr:short] [day padding:space] \
325- [hour]:[minute]:[second] [year]"
326- ) ;
327-
328- const ISO_8601_FORMAT : & [ time:: format_description:: FormatItem ] =
329- format_description ! ( "[year]-[month]-[day]" ) ;
330-
331- // "%Y%m%d%H%M.%S" 15 chars
332- const YYYYMMDDHHMM_DOT_SS_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
333- "[year repr:full][month repr:numerical padding:zero]\
334- [day][hour][minute].[second]"
335- ) ;
336-
337- // "%Y-%m-%d %H:%M:%S.%SS" 12 chars
338- const YYYYMMDDHHMMSS_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
339- "[year repr:full]-[month repr:numerical padding:zero]-\
340- [day] [hour]:[minute]:[second].[subsecond]"
341- ) ;
342-
343- // "%Y-%m-%d %H:%M:%S" 12 chars
344- const YYYYMMDDHHMMS_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
345- "[year repr:full]-[month repr:numerical padding:zero]-\
346- [day] [hour]:[minute]:[second]"
347- ) ;
348-
349- // "%Y-%m-%d %H:%M" 12 chars
350- // Used for example in tests/touch/no-rights.sh
351- const YYYY_MM_DD_HH_MM_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
352- "[year repr:full]-[month repr:numerical padding:zero]-\
353- [day] [hour]:[minute]"
354- ) ;
355-
356- // "%Y%m%d%H%M" 12 chars
357- const YYYYMMDDHHMM_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
358- "[year repr:full][month repr:numerical padding:zero]\
359- [day][hour][minute]"
360- ) ;
361-
362- // "%y%m%d%H%M.%S" 13 chars
363- const YYMMDDHHMM_DOT_SS_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
364- "[year repr:last_two padding:none][month][day]\
365- [hour][minute].[second]"
366- ) ;
367-
368- // "%y%m%d%H%M" 10 chars
369- const YYMMDDHHMM_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
370- "[year repr:last_two padding:none][month padding:zero][day padding:zero]\
371- [hour repr:24 padding:zero][minute padding:zero]"
372- ) ;
373-
374- // "%Y-%m-%d %H:%M +offset"
375- // Used for example in tests/touch/relative.sh
376- const YYYYMMDDHHMM_OFFSET_FORMAT : & [ time:: format_description:: FormatItem ] = format_description ! (
377- "[year]-[month]-[day] [hour repr:24]:[minute] \
378- [offset_hour sign:mandatory][offset_minute]"
379- ) ;
380-
381- fn parse_date ( s : & str ) -> UResult < FileTime > {
382- // This isn't actually compatible with GNU touch, but there doesn't seem to
383- // be any simple specification for what format this parameter allows and I'm
384- // not about to implement GNU parse_datetime.
385- // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
386-
387- // TODO: match on char count?
388-
389- // "The preferred date and time representation for the current locale."
390- // "(In the POSIX locale this is equivalent to %a %b %e %H:%M:%S %Y.)"
391- // time 0.1.43 parsed this as 'a b e T Y'
392- // which is equivalent to the POSIX locale: %a %b %e %H:%M:%S %Y
393- // Tue Dec 3 ...
394- // ("%c", POSIX_LOCALE_FORMAT),
395- //
396- if let Ok ( parsed) = time:: PrimitiveDateTime :: parse ( s, & POSIX_LOCALE_FORMAT ) {
397- return Ok ( local_dt_to_filetime ( to_local ( parsed) ) ) ;
398- }
399-
400- // Also support other formats found in the GNU tests like
401- // in tests/misc/stat-nanoseconds.sh
402- // or tests/touch/no-rights.sh
403- for fmt in [
404- YYYYMMDDHHMMS_FORMAT ,
405- YYYYMMDDHHMMSS_FORMAT ,
406- YYYY_MM_DD_HH_MM_FORMAT ,
407- YYYYMMDDHHMM_OFFSET_FORMAT ,
408- ] {
409- if let Ok ( parsed) = time:: PrimitiveDateTime :: parse ( s, & fmt) {
410- return Ok ( dt_to_filename ( parsed) ) ;
411- }
412- }
413-
414- // "Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)"
415- // ("%F", ISO_8601_FORMAT),
416- if let Ok ( parsed) = time:: Date :: parse ( s, & ISO_8601_FORMAT ) {
417- return Ok ( local_dt_to_filetime ( to_local (
418- time:: PrimitiveDateTime :: new ( parsed, time ! ( 00 : 00 ) ) ,
419- ) ) ) ;
420- }
421-
422- // "@%s" is "The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)"
423- if s. bytes ( ) . next ( ) == Some ( b'@' ) {
424- if let Ok ( ts) = & s[ 1 ..] . parse :: < i64 > ( ) {
425- // Don't convert to local time in this case - seconds since epoch are not time-zone dependent
426- return Ok ( local_dt_to_filetime (
427- time:: OffsetDateTime :: from_unix_timestamp ( * ts) . unwrap ( ) ,
428- ) ) ;
429- }
430- }
431-
432- if let Some ( duration) = parse_relative_time:: from_str ( s) {
433- let now_local = time:: OffsetDateTime :: now_local ( ) . unwrap ( ) ;
434- let diff = now_local. checked_add ( duration) . unwrap ( ) ;
435- return Ok ( local_dt_to_filetime ( diff) ) ;
436- }
437-
438- Err ( USimpleError :: new ( 1 , format ! ( "Unable to parse date: {s}" ) ) )
439- }
440-
441- fn parse_timestamp ( s : & str ) -> UResult < FileTime > {
442- // TODO: handle error
443- let now = time:: OffsetDateTime :: now_utc ( ) ;
444-
445- let ( mut format, mut ts) = match s. chars ( ) . count ( ) {
446- 15 => ( YYYYMMDDHHMM_DOT_SS_FORMAT , s. to_owned ( ) ) ,
447- 12 => ( YYYYMMDDHHMM_FORMAT , s. to_owned ( ) ) ,
448- 13 => ( YYMMDDHHMM_DOT_SS_FORMAT , s. to_owned ( ) ) ,
449- 10 => ( YYMMDDHHMM_FORMAT , s. to_owned ( ) ) ,
450- 11 => ( YYYYMMDDHHMM_DOT_SS_FORMAT , format ! ( "{}{}" , now. year( ) , s) ) ,
451- 8 => ( YYYYMMDDHHMM_FORMAT , format ! ( "{}{}" , now. year( ) , s) ) ,
452- _ => {
453- return Err ( USimpleError :: new (
454- 1 ,
455- format ! ( "invalid date format {}" , s. quote( ) ) ,
456- ) )
457- }
458- } ;
459- // workaround time returning Err(TryFromParsed(InsufficientInformation)) for year w/
460- // repr:last_two
461- // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1ccfac7c07c5d1c7887a11decf0e1996
462- if s. chars ( ) . count ( ) == 10 {
463- format = YYYYMMDDHHMM_FORMAT ;
464- ts = "20" . to_owned ( ) + & ts;
465- } else if s. chars ( ) . count ( ) == 13 {
466- format = YYYYMMDDHHMM_DOT_SS_FORMAT ;
467- ts = "20" . to_owned ( ) + & ts;
468- }
469-
470- let leap_sec = if ( format == YYYYMMDDHHMM_DOT_SS_FORMAT || format == YYMMDDHHMM_DOT_SS_FORMAT )
471- && ts. ends_with ( ".60" )
472- {
473- // Work around to disable leap seconds
474- // Used in gnu/tests/touch/60-seconds
475- ts = ts. replace ( ".60" , ".59" ) ;
476- true
477- } else {
478- false
479- } ;
480-
481- let tm = time:: PrimitiveDateTime :: parse ( & ts, & format)
482- . map_err ( |_| USimpleError :: new ( 1 , format ! ( "invalid date ts format {}" , ts. quote( ) ) ) ) ?;
483- let mut local = to_local ( tm) ;
484- if leap_sec {
485- // We are dealing with a leap second, add it
486- local = local. saturating_add ( Duration :: SECOND ) ;
487- }
488- let ft = local_dt_to_filetime ( local) ;
489-
490- // // We have to check that ft is valid time. Due to daylight saving
491- // // time switch, local time can jump from 1:59 AM to 3:00 AM,
492- // // in which case any time between 2:00 AM and 2:59 AM is not valid.
493- // // Convert back to local time and see if we got the same value back.
494- // let ts = time::Timespec {
495- // sec: ft.unix_seconds(),
496- // nsec: 0,
497- // };
498- // let tm2 = time::at(ts);
499- // if tm.tm_hour != tm2.tm_hour {
500- // return Err(USimpleError::new(
501- // 1,
502- // format!("invalid date format {}", s.quote()),
503- // ));
504- // }
505-
506- Ok ( ft)
507- }
508-
509304// TODO: this may be a good candidate to put in fsext.rs
510305/// Returns a PathBuf to stdout.
511306///
0 commit comments