Skip to content

Commit a0e1f16

Browse files
author
José Valim
committed
Ensure File.touch and File.touch! properly work with universal time
Operations that change mtime should work with universal times. Closes #3722.
1 parent 1cddf5a commit a0e1f16

File tree

4 files changed

+25
-16
lines changed

4 files changed

+25
-16
lines changed

lib/elixir/lib/file.ex

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -358,30 +358,33 @@ defmodule File do
358358

359359
@doc """
360360
Updates modification time (mtime) and access time (atime) of
361-
the given file. File is created if it doesn’t exist.
361+
the given file.
362+
363+
File is created if it doesn’t exist. Requires datetime in UTC.
362364
"""
363365
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
364-
def touch(path, time \\ :calendar.local_time) do
366+
def touch(path, time \\ :calendar.universal_time) do
365367
path = IO.chardata_to_string(path)
366-
case F.change_time(path, time) do
368+
case :elixir_utils.change_universal_time(path, time) do
367369
{:error, :enoent} -> touch_new(path, time)
368370
other -> other
369371
end
370372
end
371373

372374
defp touch_new(path, time) do
373375
case write(path, "", [:append]) do
374-
:ok -> F.change_time(path, time)
376+
:ok -> :elixir_utils.change_universal_time(path, time)
375377
{:error, _reason} = error -> error
376378
end
377379
end
378380

379381
@doc """
380382
Same as `touch/2` but raises an exception if it fails.
381-
Returns `:ok` otherwise.
383+
384+
Returns `:ok` otherwise. Requires datetime in UTC.
382385
"""
383386
@spec touch!(Path.t, :calendar.datetime) :: :ok | no_return
384-
def touch!(path, time \\ :calendar.local_time) do
387+
def touch!(path, time \\ :calendar.universal_time) do
385388
case touch(path, time) do
386389
:ok -> :ok
387390
{:error, reason} ->
@@ -451,16 +454,16 @@ defmodule File do
451454
It returns `:ok` in case of success, returns `{:error, reason}` otherwise
452455
453456
Note: The command `mv` in Unix systems behaves differently depending
454-
if `source` is a file and the `destination` is an existing directory.
455-
We have chosen to explicitly disallow this behaviour.
457+
if `source` is a file and the `destination` is an existing directory.
458+
We have chosen to explicitly disallow this behaviour.
456459
457460
## Examples
458461
459462
# Rename file "a.txt" to "b.txt"
460463
File.rename "a.txt", "b.txt"
461464
462465
# Rename directory "samples" to "tmp"
463-
File.rename "samples", "tmp"
466+
File.rename "samples", "tmp"
464467
"""
465468
@spec rename(Path.t, Path.t) :: :ok | {:error, posix}
466469
def rename(source, destination) do
@@ -1198,19 +1201,19 @@ defmodule File do
11981201
is specified. This means any data streamed into the file must be
11991202
converted to `iodata` type. If you pass `[:utf8]` in the modes parameter,
12001203
the underlying stream will use `IO.write/2` and the `String.Chars` protocol
1201-
to convert the data. See `IO.binwrite/2` and `IO.write/2` .
1204+
to convert the data. See `IO.binwrite/2` and `IO.write/2` .
12021205
12031206
One may also consider passing the `:delayed_write` option if the stream
12041207
is meant to be written to under a tight loop.
12051208
12061209
## Examples
1207-
1210+
12081211
# Read in 2048 byte chunks rather than lines
12091212
File.stream!("./test/test.data", [], 2048)
12101213
#=> %File.Stream{line_or_bytes: 2048, modes: [:raw, :read_ahead, :binary],
12111214
#=> path: "./test/test.data", raw: true}
12121215
1213-
See `Stream.run/1` for an example of streaming into a file.
1216+
See `Stream.run/1` for an example of streaming into a file.
12141217
12151218
"""
12161219
def stream!(path, modes \\ [], line_or_bytes \\ :line) do

lib/elixir/src/elixir_utils.erl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
-export([elixir_to_erl/1, get_line/1, split_last/1, meta_location/1,
55
characters_to_list/1, characters_to_binary/1, macro_name/1,
66
convert_to_boolean/4, returns_boolean/1, atom_concat/1,
7-
read_file_type/1, read_link_type/1, relative_to_cwd/1, erl_call/4]).
7+
read_file_type/1, read_link_type/1, relative_to_cwd/1,
8+
change_universal_time/2, erl_call/4]).
89
-include("elixir.hrl").
910
-include_lib("kernel/include/file.hrl").
1011

@@ -43,6 +44,11 @@ read_link_type(File) ->
4344
{error, _} = Error -> Error
4445
end.
4546

47+
change_universal_time(Name, {{Y, M, D}, {H, Min, Sec}}=Time)
48+
when is_integer(Y), is_integer(M), is_integer(D),
49+
is_integer(H), is_integer(Min), is_integer(Sec)->
50+
file:write_file_info(Name, #file_info{mtime=Time}, [{time, universal}]).
51+
4652
relative_to_cwd(Path) ->
4753
case elixir_compiler:get_opt(internal) of
4854
true -> Path;

lib/elixir/test/elixir/file_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1527,7 +1527,7 @@ defmodule FileTest do
15271527
refute File.exists?(fixture)
15281528
assert File.touch(fixture, time) == :ok
15291529
assert {:ok, ""} == File.read(fixture)
1530-
assert File.stat!(fixture).mtime == time
1530+
assert File.stat!(fixture, time: :universal).mtime == time
15311531
after
15321532
File.rm(fixture)
15331533
end

lib/mix/lib/mix/utils.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ defmodule Mix.Utils do
9999
end
100100

101101
def last_modified(path) do
102-
now = :calendar.local_time
102+
now = :calendar.universal_time
103103

104-
case File.stat(path) do
104+
case File.stat(path, time: :universal) do
105105
{:ok, %File.Stat{mtime: mtime}} when mtime > now ->
106106
Mix.shell.error("warning: mtime (modified time) for \"#{path}\" was set to the future, resetting to now")
107107
File.touch!(path, now)

0 commit comments

Comments
 (0)