github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/date/date.go (about) 1 // Copyright 2015-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // date prints the date. 6 // 7 // Synopsis: 8 // 9 // date [-u] [+format] | date [-u] [MMDDhhmm[CC]YY[.ss]] 10 package main 11 12 import ( 13 "flag" 14 "fmt" 15 "io" 16 "log" 17 "os" 18 "regexp" 19 "strconv" 20 "strings" 21 "time" 22 ) 23 24 type Clock interface { 25 Now() time.Time 26 } 27 28 type RealClock struct{} 29 30 func (r RealClock) Now() time.Time { 31 return time.Now() 32 } 33 34 var ( 35 // default format map from format.go on time lib 36 // Help to make format of date with posix compliant 37 fmtMap = map[string]string{ 38 "%a": "Mon", 39 "%A": "Monday", 40 "%b": "Jan", 41 "%h": "Jan", 42 "%B": "January", 43 "%c": time.UnixDate, 44 "%d": "02", 45 "%e": "_2", 46 "%H": "15", 47 "%I": "03", 48 "%m": "1", 49 "%M": "04", 50 "%p": "PM", 51 "%S": "05", 52 "%y": "06", 53 "%Y": "2006", 54 "%z": "-0700", 55 "%Z": "MST", 56 } 57 flags struct { 58 universal bool 59 reference string 60 } 61 ) 62 63 const cmd = "date [-u] [-d FILE] [+format] | date [-u] [-d FILE] [MMDDhhmm[CC]YY[.ss]]" 64 65 func init() { 66 defUsage := flag.Usage 67 flag.Usage = func() { 68 os.Args[0] = cmd 69 defUsage() 70 } 71 flag.BoolVar(&flags.universal, "u", false, "Coordinated Universal Time (UTC)") 72 flag.StringVar(&flags.reference, "r", "", "Display the last modification time of FILE") 73 } 74 75 // regex search for +format POSIX patterns 76 func formatParser(args string) []string { 77 pattern := regexp.MustCompile("%[a-zA-Z]") 78 match := pattern.FindAll([]byte(args), -1) 79 80 var results []string 81 for _, m := range match { 82 results = append(results, string(m[:])) 83 } 84 85 return results 86 } 87 88 // replace map for the format patterns according POSIX and GNU implementations 89 func dateMap(t time.Time, z *time.Location, format string) string { 90 d := t.In(z) 91 var toReplace string 92 for _, match := range formatParser(format) { 93 translate, exists := fmtMap[match] 94 switch { 95 case exists: 96 // Values defined by fmtMap 97 toReplace = d.Format(translate) 98 case match == "%C": 99 // Century (a year divided by 100 and truncated to an integer) 100 // as a decimal number [00,99]. 101 toReplace = strconv.Itoa(d.Year() / 100) 102 case match == "%D": 103 // Date in the format mm/dd/yy. 104 toReplace = dateMap(t, z, "%m/%d/%y") 105 case match == "%j": 106 // Day of the year as a decimal number [001,366]." 107 year, weekYear := d.ISOWeek() 108 firstWeekDay := time.Date(year, 1, 1, 1, 1, 1, 1, time.UTC).Weekday() 109 weekDay := d.Weekday() 110 dayYear := int(weekYear)*7 - (int(firstWeekDay) - 1) + int(weekDay) 111 toReplace = strconv.Itoa(dayYear) 112 case match == "%n": 113 // A <newline>. 114 toReplace = "\n" 115 case match == "%r": 116 // 12-hour clock time [01,12] using the AM/PM notation; 117 // in the POSIX locale, this shall be equivalent to %I : %M : %S %p. 118 toReplace = dateMap(t, z, "%I:%M:%S %p") 119 case match == "%t": 120 // A <tab>. 121 toReplace = "\t" 122 case match == "%T": 123 toReplace = dateMap(t, z, "%H:%M:%S") 124 case match == "%W": 125 // Week of the year (Sunday as the first day of the week) 126 // as a decimal number [00,53]. All days in a new year preceding 127 // the first Sunday shall be considered to be in week 0. 128 _, weekYear := d.ISOWeek() 129 weekDay := int(d.Weekday()) 130 isNotSunday := 1 131 if weekDay == 0 { 132 isNotSunday = 0 133 } 134 toReplace = strconv.Itoa(weekYear - (isNotSunday)) 135 case match == "%w": 136 // Weekday as a decimal number [0,6] (0=Sunday). 137 toReplace = strconv.Itoa(int(d.Weekday())) 138 case match == "%V": 139 // Week of the year (Monday as the first day of the week) 140 // as a decimal number [01,53]. If the week containing January 1 141 // has four or more days in the new year, then it shall be 142 // considered week 1; otherwise, it shall be the last week 143 // of the previous year, and the next week shall be week 1. 144 _, weekYear := d.ISOWeek() 145 toReplace = strconv.Itoa(int(weekYear)) 146 case match == "%x": 147 // Locale's appropriate date representation. 148 toReplace = dateMap(t, z, "%m/%d/%y") // TODO: decision algorithm 149 case match == "%F": 150 // Date yyyy-mm-dd defined by GNU implementation 151 toReplace = dateMap(t, z, "%Y-%m-%d") 152 case match == "%X": 153 // Locale's appropriate time representation. 154 toReplace = dateMap(t, z, "%I:%M:%S %p") // TODO: decision algorithm 155 default: 156 continue 157 } 158 159 format = strings.Replace(format, match, toReplace, 1) 160 // fmt.Printf("Iteration[%d]: %v\n", iter, format) 161 } 162 return format 163 } 164 165 func ints(s string, i ...*int) error { 166 var err error 167 for _, p := range i { 168 if *p, err = strconv.Atoi(s[0:2]); err != nil { 169 return err 170 } 171 s = s[2:] 172 } 173 return nil 174 } 175 176 // getTime gets the desired time as a time.Time. 177 // It derives it from a unix date command string. 178 // Some values in the string are optional, namely 179 // YY and SS. For these values, we use 180 // time.Now(). For the timezone, we use whatever 181 // one we are in, or UTC if desired. 182 func getTime(z *time.Location, s string, clocksource Clock) (t time.Time, err error) { 183 var MM, DD, hh, mm int 184 // CC is the year / 100, not the "century". 185 // i.e. for 2001, CC is 20, not 21. 186 YY := clocksource.Now().Year() % 100 187 CC := clocksource.Now().Year() / 100 188 SS := clocksource.Now().Second() 189 if err = ints(s, &MM, &DD, &hh, &mm); err != nil { 190 return 191 } 192 s = s[8:] 193 switch len(s) { 194 case 0: 195 case 2: 196 err = ints(s, &YY) 197 case 3: 198 err = ints(s[1:], &SS) 199 case 4: 200 err = ints(s, &CC, &YY) 201 case 5: 202 s = s[0:2] + s[3:] 203 err = ints(s, &YY, &SS) 204 case 7: 205 s = s[0:4] + s[5:] 206 err = ints(s, &CC, &YY, &SS) 207 default: 208 err = fmt.Errorf("optional string is %v instead of [[CC]YY][.ss]", s) 209 } 210 211 if err != nil { 212 return 213 } 214 215 YY = YY + CC*100 216 t = time.Date(YY, time.Month(MM), DD, hh, mm, SS, 0, z) 217 return 218 } 219 220 func date(t time.Time, z *time.Location) string { 221 return t.In(z).Format(time.UnixDate) 222 } 223 224 func run(args []string, univ bool, ref string, clocksource Clock, w io.Writer) error { 225 t := clocksource.Now() 226 z := time.Local 227 if univ { 228 z = time.UTC 229 } 230 if ref != "" { 231 stat, err := os.Stat(ref) 232 if err != nil { 233 return fmt.Errorf("unable to gather stats of file %v", ref) 234 } 235 t = stat.ModTime() 236 } 237 238 switch len(args) { 239 case 0: 240 fmt.Fprintf(w, "%v\n", date(t, z)) 241 case 1: 242 a0 := args[0] 243 if strings.HasPrefix(a0, "+") { 244 fmt.Fprintf(w, "%v\n", dateMap(t, z, a0[1:])) 245 } else { 246 if err := setDate(args[0], z, clocksource); err != nil { 247 return fmt.Errorf("%v: %v", a0, err) 248 } 249 } 250 default: 251 flag.Usage() 252 return nil 253 } 254 return nil 255 } 256 257 func main() { 258 flag.Parse() 259 rc := RealClock{} 260 if err := run(flag.Args(), flags.universal, flags.reference, rc, os.Stdout); err != nil { 261 log.Fatalf("date: %v", err) 262 } 263 }