summaryrefslogtreecommitdiff
path: root/doc/file
diff options
context:
space:
mode:
Diffstat (limited to 'doc/file')
-rw-r--r--doc/file/filename_globbing.md301
-rw-r--r--doc/file/filename_matching.md356
-rw-r--r--doc/file/timestamps.md83
3 files changed, 740 insertions, 0 deletions
diff --git a/doc/file/filename_globbing.md b/doc/file/filename_globbing.md
new file mode 100644
index 0000000000..7981964c5c
--- /dev/null
+++ b/doc/file/filename_globbing.md
@@ -0,0 +1,301 @@
+# Filename Globbing
+
+Filename globbing is a pattern-matching feature implemented in certain Ruby methods.
+
+Filename-globbing methods find filesystem entries (files and directories)
+that match certain patterns;
+these methods are:
+
+- Dir.glob.
+- [`Dir[]`](https://docs.ruby-lang.org/en/master/Dir.html#method-c-5B-5D).
+- Pathname.glob.
+- Pathname#glob.
+
+These methods are quite different from filename-matching methods (not discussed here),
+which match patterns against string paths, and do not access the filesystem;
+those methods are:
+
+- File.fnmatch.
+- Pathname#fnmatch.
+
+These are the basic elements of filename-globbing patterns;
+see the sections below for details:
+
+| Pattern | Meaning | Examples |
+|:------------------------:|------------------------------------------|------------------------------|
+| Simple string. | Matches itself. | `'LEGAL'` |
+| `'*'` | Matches any sequence of characters. | `'*.txt'` |
+| `'?'` | Matches any single character. | `'?.txt'` |
+| `'[abc]'`,<br>`'[^abc]'` | Matches a single character from a set. | `'x[abc]y'`,<br>`'x[^abc]y'` |
+| `'[a-z]`',<br>`'[^a-z]'` | Matches a single character from a range. | `'x[0-9]y'`,<br>`'x[^0-9]y'` |
+| `'{ , }'` | Matches alternatives. | `'{abc,def}'` |
+| `'**'` | Matches directories recursively. | `'**/test.rb'` |
+| `'\'` | Escapes the next character. | `'\\*'`, `'\?'` |
+
+## Patterns
+
+### Simple \String
+
+A "simple string" is one that does not contain special filename-globbing patterns;
+see the table above.
+
+A simple string matches itself:
+
+```ruby
+Dir.glob('LEGAL') # => ["LEGAL"]
+Dir.glob('LEGA') # => [] # Must be exact.
+Dir.glob('legal') # => [] # Case-sensitive.
+```
+
+Note that case-sensitivity may _not_ be modified by flags.
+
+By default, the Windows short name pattern is disabled:
+
+```ruby
+Dir.glob('PROGRAM~1') # => []
+```
+
+It may be enabled by flag [`File::FNM_SHORTNAME`](#constant-filefnmshortname).
+
+
+### Any Sequence of Characters (`'*'`)
+
+The asterisk pattern (`'*'`) matches any sequence of characters:
+
+```ruby
+Dir.glob('*').take(3) # => ["BSDL", "CONTRIBUTING.md", "COPYING"]
+Dir.glob('\*') # => [] # Escaped.
+```
+
+By default, the asterisk pattern does not match a leading period (as in a dot-file):
+
+```ruby
+Dir.glob('*').select {|entry| entry.start_with?('.') } # => []
+```
+
+That matching may be enabled by flag [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch).
+
+The asterisk pattern does not match across file separators:
+
+```ruby
+Dir.glob('*.rb').select {|entry| entry.include?('/') } # => []
+```
+
+Therefore flag File::FNM_PATHNAME does not affect the pattern.
+
+### Single Character (`'?'`)
+
+The question-mark pattern (`'?'`) matches any single character:
+
+```ruby
+Dir.glob('???') # => ["GPL", "bin", "doc", "enc", "ext", "jit", "lib", "man"]
+Dir.glob('??') # => ["gc"] # Only one entry with a 2-character name.
+Dir.glob('?') # => [] # No entries with a 1-character name.
+Dir.glob('\?') # => [] # No entries containing character '?'.
+```
+
+By default, the question-mark pattern does not match a leading period (as in a dot-file):
+
+```ruby
+Dir.glob(".???") # => [".git"]
+Dir.glob("????").select {|entry| entry.start_with?('.') } # => []
+```
+
+That matching may be enabled by flag [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch).
+
+### Single Character from a Set (`'[abc]'`, `'[^abc]'`)
+
+Characters enclosed in square brackets define a set of characters,
+any of which matches a single character:
+
+```ruby
+Dir.glob('[efgh][abcd]') # => ["gc"]
+Dir.glob('\[efgh][abcd]') # => [] # Escaped.
+```
+
+The character set may be negated:
+
+```ruby
+Dir.glob('[^abcd][^efgh]') # => ["gc"]
+```
+
+### Single Character from a \Range (`'[a-c]'`, `'[^a-c]'`)
+
+A range of characters enclosed in square brackets defines a set of characters,
+any of which matches a single character:
+
+```ruby
+Dir.glob('[k-m][h-j][a-c]') # => ["lib"]
+Dir.glob('\[k-m][h-j][a-c]') # => [] # Escaped.
+```
+
+The range may be negated:
+
+```ruby
+Dir.glob('[^k-m][h-j][a-c]') # => []
+Dir.glob('[^a-c][^k-m][^h-j]') # => ["GPL", "doc", "enc", "ext", "jit", "lib", "man"]
+```
+
+### Alternatives (`'{ , }'`)
+
+The alternatives pattern consists of comma-separated strings
+enclosed in curly braces:
+
+```ruby
+Dir.glob('{k,L,R}*') # => ["kernel.rb", "LEGAL", "README.ja.md", "README.md"]
+Dir.glob('{R,L,k}*') # => ["README.ja.md", "README.md", "LEGAL", "kernel.rb"]
+# Whitespace matters:
+Dir.glob('{k ,L,R}*') # => ["LEGAL", "README.ja.md", "README.md"]
+```
+
+### Recursive Directory Matching (`'**'`)
+
+The double-asterisk pattern (`'**'`) matches directories recursively:
+
+```ruby
+# Find all entries everywhere ending with '.ja'.
+Dir.glob('**/*.ja')
+# => ["COPYING.ja", "doc/pty/README.expect.ja", "doc/pty/README.ja"]
+
+# Find all entries everywhere ending with '.rb'.
+Dir.glob('**/*.rb').size # => 7574
+Dir.glob('**/*.rb').take(3)
+# => ["KNOWNBUGS.rb", "array.rb", "ast.rb"]
+
+# Find all entries in directory 'lib' ending with `.rb'.
+Dir.glob('lib/**/*.rb').size # => 626
+Dir.glob('lib/**/*.rb').take(3)
+# # =>
+# ["lib/English.rb",
+# "lib/bundled_gems.rb",
+# "lib/bundler/build_metadata.rb"]
+
+# Find all entries in directory 'test/ruby' ending with '.rb'.
+Dir.glob('test/ruby/**/*.rb').size # => 200
+Dir.glob('test/ruby/**/*.rb').take(3)
+# # =>
+# ["test/ruby/allpairs.rb",
+# "test/ruby/beginmainend.rb",
+# "test/ruby/box/a.1_1_0.rb"]
+
+# Escaped.
+Dir.glob('\**/*.rb') # => []
+```
+
+
+### Escape (`'\'`)
+
+The backslash character (`'\'`) may be used to escape any of the characters
+that filename globbing treats as special:
+
+```ruby
+Dir.glob('\*') # => []
+Dir.glob('\?') # => []
+Dir.glob('\[efgh][abcd]') # => []
+Dir.glob('\[k-m][h-j][a-c]') # => []
+Dir.glob('\**/*.rb') # => []
+```
+
+## Keyword Arguments
+
+| Keyword | Value | Default | Meaning |
+|-------------------|--------------------------|:-------:|-----------------------------------------|
+| [`base`](#base) | \String path. | `'.'` | Root for searching. |
+| [`flags`](#flags) | Logical OR of constants. | `0` | Modify globbing behavior. |
+| [`sort`](#sort) | `true` or `false` | `true` | Whether returned array is to be sorted. |
+
+### `base`
+
+Optional keyword argument `base` (defaults to `'.'`)
+specifies where in the filesystem the searching is to begin:
+
+```ruby
+Dir.glob('*').size # => 241
+Dir.glob('*').take(3)
+# => ["BSDL", "CONTRIBUTING.md", "COPYING"]
+
+Dir.glob('*', base: 'lib').size # => 72
+Dir.glob('*', base: 'lib').take(3)
+# => ["English.gemspec", "English.rb", "bundled_gems.rb"]
+
+Dir.glob('*', base: 'lib/net').size # => 5
+Dir.glob('*', base: 'lib/net').take(3)
+# => ["http", "http.rb", "https.rb"]
+```
+
+### `flags`
+
+Optional keyword argument `flags` (defaults to `0`) may be the bitwise OR
+of the constants `File::FNM*`:
+
+```ruby
+Dir.glob('*', flags: File::FNM_DOTMATCH | File::FNM_NOESCAPE)
+```
+
+These are the constants for filename-globbing patterns;
+see the sections below for details:
+
+
+| Constant | Meaning |
+|-----------------------------------------------------|--------------------------------------------|
+| [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch) | Make pattern `'*'` match a leading period. |
+| [`File::FNM_NOESCAPE`](#constant-filefnmnoescape) | Disable escaping. |
+| [`File::FNM_SHORTNAME`](#constant-filefnmshortname) | Enable short-name matching (Windows only). |
+
+These constants do not affect filename globbing:
+
+- File::FNM_CASEFOLD.
+- File::FNM_EXTGLOB.
+- File::FNM_PATHNAME.
+- File::FNM_SYSCASE.
+
+#### Constant File::FNM_DOTMATCH
+
+By default, filename globbing does not allow patterns `'*'` and `'?'` to match a dotfile name
+(i.e, an entry name beginning with a dot);
+use constant [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch)
+to enable the match:
+
+```ruby
+Dir.glob('*').size # => 241
+Dir.glob('*', flags: File::FNM_DOTMATCH).size # => 256
+Dir.glob('*', flags: File::FNM_DOTMATCH).take(3) # => [".", ".dir-locals.el", ".document"]
+```
+
+#### Constant File::FNM_NOESCAPE
+
+By default filename globbing has escaping enabled;
+use constant [`File::FNM_NOESCAPE`](#constant-filefnmnoescape)
+to disable it:
+
+```ruby
+Dir.glob('*').size # => 241
+Dir.glob('\*').size # => 0
+```
+
+#### Constant File::FNM_SHORTNAME
+
+By default, Windows shortname matching is disabled;
+use constant [`File::FNM_SHORTNAME`](#constant-filefnmshortname)
+to enable it (on Windows only).
+
+Using that constant allows patterns to match short names
+in filename globbing on Windows,
+which can be useful for compatibility with legacy applications
+that rely on these short names;
+see [8.3 filename](https://en.wikipedia.org/wiki/8.3_filename).
+This feature helps ensure that file operations work correctly
+even when dealing with files that have long names.
+
+### `sort`
+
+Optional keyword argument `sort` (defaults to `'true'`)
+specifies whether the returned array is to be sorted:
+
+```ruby
+Dir.glob('*').take(3)
+# => ["BSDL", "CONTRIBUTING.md", "COPYING"]
+Dir.glob('*', sort: false).take(3)
+# => ["gc.rb", "yjit.rb", "iseq.h"]
+```
+
diff --git a/doc/file/filename_matching.md b/doc/file/filename_matching.md
new file mode 100644
index 0000000000..9f13da9012
--- /dev/null
+++ b/doc/file/filename_matching.md
@@ -0,0 +1,356 @@
+## Filename Matching
+
+Filename matching is a pattern-matching feature implemented in certain Ruby methods:
+
+- File.fnmatch.
+- Pathname#fnmatch.
+
+Each `fnmatch` method matches a pattern against a string _path_;
+these methods operate only on strings, and do not access the file system.
+
+These are quite different from filename globbing methods (not discussed here),
+which match patterns against string paths found in the actual file system:
+
+- Dir.glob.
+- Pathname.glob.
+- Pathname#glob.
+
+### Patterns
+
+These are the basic elements of filename matching patterns;
+see the sections below for details:
+
+| Pattern | Meaning | Examples |
+|:------------------------:|--------------------------------------------|------------------------------|
+| Simple string. | Matches itself. | `'Rakefile'`, `'LEGAL'` |
+| `'*'` | Matches any sequence of characters. | `'*.txt'` |
+| `'?'` | Matches any single character. | `'?.txt'` |
+| `'[abc]'`,<br>`'[^abc]'` | Matches a single character from a set. | `'x[abc]y'`,<br>`'x[^abc]y'` |
+| `'[a-z]`',<br>`'[^a-z]'` | Matches a single character from a range. | `'x[0-9]y'`,<br>`'x[^0-9]y'` |
+| `'\'` | Escapes the next character. | `'\\*'`, `'\?'` |
+
+There are two other patterns that are disabled by default:
+
+- Directory-like substring (`'**'`);
+ see [`File::FNM_PATHNAME`](#constant-filefnmpathname) below.
+- Alternatives (`'{ , }'`);
+ see [`File::FNM_EXTGLOB`](#constant-filefnmextglob) below.
+
+#### Simple \String
+
+A "simple string" is one that does not contain special filename-matching patterns;
+see the table above.
+
+A simple string matches itself:
+
+```ruby
+File.fnmatch('xyzzy', 'xyzzy') # => true
+File.fnmatch('one_two_three', 'one_two_three') # => true
+File.fnmatch('123', '123') # => true
+File.fnmatch('Form 27B/6', 'Form 27B/6') # => true
+File.fnmatch('bcd', 'abcde') # => false # Must be exact.
+```
+
+By default, the matching is case-sensitive:
+
+```ruby
+File.fnmatch('abc', 'ABC') # => false
+```
+
+Case-sensitivity may be modified by flags:
+
+- [`File::FNM_CASEFOLD`](#constant-filefnmcasefold).
+- [`File::FNM_SYSCASE`](#constant-filefnmsyscase).
+
+By default, the alternatives pattern is disabled:
+
+```rutby
+File.fnmatch('R{ub,foo}y', 'Ruby') # => false
+```
+
+It may be enabled by flag [`File::FNM_EXTGLOB`](#constant-filefnmextglob).
+
+By default, the Windows short name pattern is disabled:
+
+```ruby
+File.fnmatch('PROGRAM~1', 'Program Files') # => false
+```
+
+It may be enabled by flag [`File::FNM_SHORTNAME`](#constant-filefnmshortname).
+
+#### Any Sequence of Characters (`'*'`)
+
+The asterisk pattern (`'*'`) matches any sequence of characters:
+
+```ruby
+File.fnmatch('*', 'foo') # => true
+File.fnmatch('*', '') # => true
+File.fnmatch('*', '*') # => true
+File.fnmatch('\*', 'foo') # => false # Escaped.
+```
+
+By default, the asterisk pattern does not match a leading period (as in a dot-file):
+
+```ruby
+File.fnmatch('*', '.document') # => false
+```
+
+That matching may be enabled by flag [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch).
+
+By default, the asterisk pattern matches across file separators:
+
+```ruby
+File.fnmatch('*.rb', 'lib/test.rb') # => true
+```
+
+That matching may be disabled by flag [`File::FNM_PATHNAME`](#constant-filefnmpathname).
+
+#### Single Character (`'?'`)
+
+The question-mark pattern (`'?'`) matches any single character:
+
+```ruby
+File.fnmatch('?', 'f') # => true
+File.fnmatch("foo-?.txt", "foo-1.txt") # => true
+File.fnmatch('?', 'foo') # => false
+File.fnmatch('?', '') # => false
+File.fnmatch('\?', 'f') # => false # Escaped.
+```
+
+By default, pattern `'?'` matches the file separator:
+
+```ruby
+File.fnmatch('foo?boo', 'foo/boo') # => true
+```
+
+That matching may be disabled by flag [`File::FNM_PATHNAME`](#constant-filefnmpathname).
+
+#### Single Character from a Set (`'[abc]'`, `'[^abc]'`)
+
+Characters enclosed in square brackets define a set of characters,
+any of which matches a single character:
+
+```ruby
+File.fnmatch('[ruby]', 'r') # => true
+File.fnmatch('[ruby]', 'u') # => true
+File.fnmatch('[ruby]', 'y') # => true
+File.fnmatch('[ruby]', 'ruby') # => false
+File.fnmatch('\[ruby]', 'r') # => false # Escaped.
+```
+
+The character set may be negated:
+
+```ruby
+File.fnmatch('[^ruby]', 'r') # => false
+File.fnmatch('[^ruby]', 'u') # => false
+```
+
+#### Single Character from a \Range (`'[a-c]'`, `'[^a-c]'`)
+
+A range of characters enclosed in square brackets defines a set of characters,
+any of which matches a single character:
+
+```ruby
+File.fnmatch('[a-c]', 'b') # => true
+File.fnmatch('[a-c]', 'd') # => false
+File.fnmatch('[a-c]', 'abc') # => false
+File.fnmatch('R[t-v][a-c]y', 'Ruby') # => true # Multiple ranges allowed.
+File.fnmatch('\[a-c]', 'b') # => false # Escaped.
+```
+
+The range may be negated:
+
+```ruby
+File.fnmatch('[^a-c]', 'b') # => false
+File.fnmatch('[^a-c]', 'd') # => true
+```
+
+#### Escape (`'\'`)
+
+The backslash character (`'\'`) may be used to escape any of the characters
+that filename matching treats as special:
+
+```ruby
+File.fnmatch('[a-c]', 'b') # => true
+File.fnmatch('\[a-c]', 'b') # => false
+File.fnmatch('[a-c\]', 'b') # => false
+File.fnmatch('[a\-c]', 'b') # => false
+
+File.fnmatch('{a,b}', 'b', File::FNM_EXTGLOB) # => true
+File.fnmatch('\{a,b}', 'b', File::FNM_EXTGLOB) # => false
+File.fnmatch('{a\,b}', 'b', File::FNM_EXTGLOB) # => false
+File.fnmatch('{a,b\}', 'b', File::FNM_EXTGLOB) # => false
+```
+
+Use a double-backslash to represent an ordinary backslash:
+
+```ruby
+File.fnmatch('\\\\', '\\') # => true
+```
+
+By default escape pattern `'\'` is enabled;
+it may be disabled by flag [`File::FNM_NOESCAPE`](#constant-filefnmnoescape).
+
+### Flags
+
+Optional argument `flags` (defaults to `0`) may be the bitwise OR
+of the constants `File::FNM*`.
+
+These are the constants for filename-matching patterns;
+see the sections below for details:
+
+| Constant | Meaning |
+|-----------------------------------------------------|-------------------------------------------------------------|
+| [`File::FNM_CASEFOLD`](#constant-filefnmcasefold) | Make the pattern case-insensitive. |
+| [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch) | Make pattern `*` match a leading period.. |
+| [`File::FNM_EXTGLOB`](#constant-filefnmextglob) | Enable alternatives in pattern. |
+| [`File::FNM_NOESCAPE`](#constant-filefnmnoescape) | Disable escaping. |
+| [`File::FNM_PATHNAME`](#constant-filefnmpathname) | Make patterns `'*'` and `'?'` not match the file separator. |
+| [`File::FNM_SHORTNAME`](#constant-filefnmshortname) | Enable short-name matching (Windows only). |
+| [`File::FNM_SYSCASE`](#constant-filefnmsyscase) | Make the pattern use OS's case sensitivity. |
+
+
+#### Constant File::FNM_CASEFOLD
+
+By default, filename matching is case-sensitive;
+use constant [`File::FNM_CASEFOLD`](#constant-filefnmcasefold)
+to make the matching case-insensitive:
+
+```ruby
+File.fnmatch('abc', 'ABC') # => false
+File.fnmatch('abc', 'ABC', File::FNM_CASEFOLD) # => true
+```
+
+#### Constant File::FNM_DOTMATCH
+
+By default, filename matching does not allow pattern `'*'` to match a dotfile name
+(i.e, a filename beginning with a dot);
+use constant [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch)
+to enable the match:
+
+```ruby
+File.fnmatch('*', '.document') # => false
+File.fnmatch('*', '.document', File::FNM_DOTMATCH) # => true
+```
+#### Constant File::FNM_EXTGLOB
+
+By default, filename matching has the alternative notation disabled;
+use constant [`File::FNM_EXTGLOB`](#constant-filefnmextglob)
+to enable it:
+
+```ruby
+File.fnmatch('R{ub,foo}y', 'Ruby') # => false
+File.fnmatch('R{ub,foo}y', 'Ruby', File::FNM_EXTGLOB) # => true
+```
+
+The alternatives pattern consists of zero or more unquoted strings,
+separated by commas, and enclosed in curly braces:
+
+```ruby
+File.fnmatch('R{ub,foo,bar}y', 'Ruby') # => false # Not enabled.
+File.fnmatch('R{ub,foo,bar}y', 'Ruby', File::FNM_EXTGLOB) # => true
+# Whitespace matters.
+File.fnmatch('R{ub ,foo,bar}y', 'Ruby', File::FNM_EXTGLOB) # => false
+File.fnmatch('R{ ub,foo,bar}y', 'Ruby', File::FNM_EXTGLOB) # => false
+# Special characters remain in force:
+File.fnmatch('{*,?}', 'hello', File::FNM_EXTGLOB) # => true
+File.fnmatch('{*ello,?}', 'hello', File::FNM_EXTGLOB) # => true
+File.fnmatch('{*ELLO,?}', 'hello', File::FNM_EXTGLOB) # => false
+File.fnmatch('{*ELLO,?????}', 'hello', File::FNM_EXTGLOB) # => true
+# With the flag not given.
+File.fnmatch('R{ub,foo,bar}y', 'Ruby') # => false
+```
+
+#### Constant File::FNM_NOESCAPE
+
+By default filename matching has escaping enabled;
+use constant [`File::FNM_NOESCAPE`](#constant-filefnmnoescape)
+to disable it:
+
+```ruby
+File.fnmatch('\*\?\*\*', '*?**') # => true
+File.fnmatch('\*\?\*\*', '*?**', File::FNM_NOESCAPE) # => false
+```
+
+#### Constant File::FNM_PATHNAME
+
+Flag [`File::FNM_PATHNAME`](#constant-filefnmpathname) affects
+patterns `'**'`, `'*'`, and `'?'`.
+
+By default, the double-asterisk pattern (`'**'`) is equivalent to pattern `'*'`,
+and matches any sequence of directory-like substrings:
+
+```ruby
+File.fnmatch('**', 'a/b/c') # => true
+File.fnmatch('*', 'a/b/c') # => true
+```
+
+When flag [`File::FNM_PATHNAME`](#constant-filefnmpathname) is given,
+the pattern matches only one component of a file path:
+
+```ruby
+File.fnmatch('**', 'a/b/c') # => true # Matches 'a/b/c'.
+File.fnmatch('**', 'a/b/c', File::FNM_PATHNAME) # => false # Matches only 'a'.
+File.fnmatch('**', 'a/b/c', File::FNM_PATHNAME) # => false # Matches only 'a/b'.
+File.fnmatch('**/*', 'a/b/c', File::FNM_PATHNAME) # => true # Matches 'a/b', then 'c'.
+```
+
+By default, filename matching enables pattern `'*'` to match
+at or across the file separator (`File::SEPARATOR`);
+use constant [`File::FNM_PATHNAME`](#constant-filefnmpathname)
+to disable such matching:
+
+```ruby
+File::SEPARATOR # => "/"
+File.fnmatch('*.rb', 'lib/test.rb') # => true
+File.fnmatch('*.rb', 'lib/test.rb', File::FNM_PATHNAME) # => false
+```
+
+By default, filename matching enables pattern `'?'` to match
+at or across the file separator (`File::SEPARATOR`);
+use constant [`File::FNM_PATHNAME`](#constant-filefnmpathname)
+to disable such matching:
+
+```ruby
+File.fnmatch('foo?boo', 'foo/boo') # => true
+File.fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME) # => false
+```
+
+#### Constant File::FNM_SHORTNAME
+
+By default, Windows shortname matching is disabled;
+use constant [`File::FNM_SHORTNAME`](#constant-filefnmshortname)
+to enable it (on Windows only).
+
+Using that constant allows patterns to match short names
+in filename matching on Windows,
+which can be useful for compatibility with legacy applications
+that rely on these short names;
+see [8.3 filename](https://en.wikipedia.org/wiki/8.3_filename).
+This feature helps ensure that file operations work correctly
+even when dealing with files that have long names.
+
+```ruby
+File::FNM_SHORTNAME.zero? # => false # On Windows, not zero; may be enabled.
+File::FNM_SHORTNAME.zero? # => true # Elsewhere, always zero; may not be enabled.
+
+File.fnmatch('PROGRAM~1', 'Program Files') # => false
+# This will be true if and only if on Windows and short name 'PROGRAM~1' exists.
+File.fnmatch('PROGRAM~1', 'Program Files', File::FNM_SHORTNAME) # => true
+```
+
+#### Constant File::FNM_SYSCASE
+
+By default, filename matching uses Ruby's own case-sensitivity rules;
+use constant [`File::FNM_SYSCASE`](#constant-filefnmsyscase)
+to use the case-sensitivity rules of the underlying file system:
+
+```ruby
+File::FNM_SYSCASE.zero? # => false # On Windows, not zero; may be enabled.
+File::FNM_SYSCASE.zero? # => true # Elsewhere, always zero; may not be enabled.
+
+File.fnmatch('abc', 'ABC') # => false # Ruby; case-sensitive.
+File.fnmatch('abc', 'ABC', File::FNM_SYSCASE) # => true # Windows; case-insensitive.
+File.fnmatch('abc', 'ABC', File::FNM_SYSCASE) # => false # Linus; case-sensitive.
+```
+
diff --git a/doc/file/timestamps.md b/doc/file/timestamps.md
new file mode 100644
index 0000000000..c8ad616567
--- /dev/null
+++ b/doc/file/timestamps.md
@@ -0,0 +1,83 @@
+# \File System Timestamps
+
+A file system entry (the name of a file or directory)
+has several times (called timestamps) associated with it.
+
+The Ruby methods that return these timestamps (each as a Time object)
+are actually returning "whatever the OS says,"
+and so their behaviors may vary among OS platforms.
+If a platform does not support a particular timestamp,
+the corresponding Ruby methods raise NotImplementedError.
+
+These timestamps are:
+
+| Name | Meaning | Changes |
+|:--------------------------------:|----------------------------------------|-----------------------|
+| [`birthtime`](#birth-time) | Create time. | Never. |
+| [`mtime`](#modification-time) | Modification time. | When written. |
+| [`atime`](#access-time) | Access time. | When read or written. |
+| [`ctime`](#metadata-change-time) | Metadata-change time (or create time). | See below. |
+
+## Birth \Time
+
+The birth time for an entry is the time the entry was created.
+The birth time does not change, although if the entry is deleted and re-created,
+the birth time will be different.
+
+Each of these methods returns the birth time for an entry as a Time object:
+
+- File::birthtime.
+- File#birthtime.
+- File::Stat#birthtime.
+- Pathname#birthtime.
+
+On Windows, each of these methods also returns the birth time:
+
+- File::ctime.
+- File#ctime.
+- File::Stat#ctime.
+- Pathname#ctime.
+
+## Modification \Time
+
+The modification time for an entry is the time the entry was last modified.
+The modification time is updated when the entry is written,
+though some file systems may delay the update.
+
+Each of these methods returns the modification time for an entry as a Time object:
+
+- File::mtime.
+- File#mtime.
+- File::Stat#mtime.
+- Pathname#mtime.
+
+## Access \Time
+
+The access time for an entry is the time the entry last read.
+The access time is updated when the entry is read,
+though some file systems may delay the update.
+
+Each of these methods returns the access time for an entry as a Time object:
+
+- File::atime.
+- File#atime.
+- File::Stat#atime.
+- Pathname#atime.
+
+## Metadata-Change \Time
+
+The metadata-change time for an entry is the time the entry last read.
+The metadata-change time is updated when the entry's metadata is changed;
+changing access mode or permissions may update the metadata-change time,
+though some file systems may delay the update.
+
+On non-Windows systems,
+each of these methods returns the metadata-change time for an entry:
+
+- File::ctime.
+- File#ctime.
+- File::Stat#ctime.
+- Pathname#ctime.
+
+On Windows, each `ctime` method returns the birth time,
+not the metadata-change time.