feat: Optionally remove seconds from datetime (#8531)
* fix: Add updated datepicker; fixed seconds formatting bug. Seconds between 0 and 9 were not zero-padded. * feat: Add framework for time format * feat: datetime server-side formatters. * tests: Added server-side datetime formatter tests * feat: Update client-side datetime formatters * tests: Add Cypress client-side formatting tests. * fix: JSON errors * fix: Update to not hard-code admin password * fix: Change to using bulk_update rather than the REST API * tests: Use Custom doctype for testing, not Standard * fix: Codacy style fixes * fix: Commonify update_datetime_picker in date.js, datetime.js, time.js Fix order of time_format in System Settings Restore get_user_fmt in utils/datetime.js * feat: Drastically reduce scale of Cypress testing (to make tests faster) Full testing is possible by setting 'fast_mode' to false in the spec file. * fix: Fix issues with datepicker/timepicker expansion * fix: typo * style: Various style fixes as requested by DeppSource: Python * fix: Timepicker not hiding on 'now' button. Force hiding on click. * style: Codacy style fixes. * fix: Use datepicker from node_modules * test: Refactor Datetime UI tests - cy.get_field - cy.set_value - cy.insert_doc with ignore_duplicate - Nominal datetime tests to cover most formats - Formatting with prettier * test: Datetime UI tests; wait for cur_frm.doc.datetime to update * tests: Add whitespace to typed input - Clear input only for Time field * test: Wait timeout 200 * test: Fix form test Co-authored-by: Faris Ansari <netchampfaris@users.noreply.github.com>
This commit is contained in:
parent
753e8d5bac
commit
07cedc581d
26 changed files with 794 additions and 488 deletions
48
cypress/fixtures/datetime_doctype.js
Normal file
48
cypress/fixtures/datetime_doctype.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
export default {
|
||||
name: 'DateTime Test',
|
||||
custom: 1,
|
||||
actions: [],
|
||||
creation: '2019-03-15 06:29:07.215072',
|
||||
doctype: 'DocType',
|
||||
editable_grid: 1,
|
||||
engine: 'InnoDB',
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'date',
|
||||
fieldtype: 'Date',
|
||||
label: 'Date'
|
||||
},
|
||||
{
|
||||
fieldname: 'time',
|
||||
fieldtype: 'Time',
|
||||
label: 'Time'
|
||||
},
|
||||
{
|
||||
fieldname: 'datetime',
|
||||
fieldtype: 'Datetime',
|
||||
label: 'Datetime'
|
||||
}
|
||||
],
|
||||
issingle: 1,
|
||||
links: [],
|
||||
modified: '2019-12-09 14:40:53.127615',
|
||||
modified_by: 'Administrator',
|
||||
module: 'Custom',
|
||||
owner: 'Administrator',
|
||||
permissions: [
|
||||
{
|
||||
create: 1,
|
||||
delete: 1,
|
||||
email: 1,
|
||||
print: 1,
|
||||
read: 1,
|
||||
role: 'System Manager',
|
||||
share: 1,
|
||||
write: 1
|
||||
}
|
||||
],
|
||||
quick_entry: 1,
|
||||
sort_field: 'modified',
|
||||
sort_order: 'ASC',
|
||||
track_changes: 1
|
||||
};
|
||||
|
|
@ -6,8 +6,8 @@ context('API Resources', () => {
|
|||
});
|
||||
|
||||
it('Creates two Comments', () => {
|
||||
cy.create_doc('Comment', {comment_type: 'Comment', content: "hello"});
|
||||
cy.create_doc('Comment', {comment_type: 'Comment', content: "world"});
|
||||
cy.insert_doc('Comment', {comment_type: 'Comment', content: "hello"});
|
||||
cy.insert_doc('Comment', {comment_type: 'Comment', content: "world"});
|
||||
});
|
||||
|
||||
it('Lists the Comments', () => {
|
||||
|
|
@ -25,11 +25,11 @@ context('API Resources', () => {
|
|||
});
|
||||
|
||||
it('Gets each Comment', () => {
|
||||
cy.get_list('Comment').then(body => body.data.forEach(comment => {
|
||||
cy.get_list('Comment').then(body => body.data.forEach(comment => {
|
||||
cy.get_doc('Comment', comment.name);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('Removes the Comments', () => {
|
||||
cy.get_list('Comment').then(body => body.data.forEach(comment => {
|
||||
cy.remove_doc('Comment', comment.name);
|
||||
|
|
|
|||
128
cypress/integration/datetime.js
Normal file
128
cypress/integration/datetime.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import datetime_doctype from '../fixtures/datetime_doctype';
|
||||
const doctype_name = datetime_doctype.name;
|
||||
|
||||
context('Control Date, Time and DateTime', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.insert_doc('DocType', datetime_doctype, true);
|
||||
});
|
||||
|
||||
describe('Date formats', () => {
|
||||
let date_formats = [
|
||||
{
|
||||
date_format: 'dd-mm-yyyy',
|
||||
part: 2,
|
||||
length: 4,
|
||||
separator: '-'
|
||||
},
|
||||
{
|
||||
date_format: 'mm/dd/yyyy',
|
||||
part: 0,
|
||||
length: 2,
|
||||
separator: '/'
|
||||
}
|
||||
];
|
||||
|
||||
date_formats.forEach(d => {
|
||||
it('test date format ' + d.date_format, () => {
|
||||
cy.set_value('System Settings', 'System Settings', {
|
||||
date_format: d.date_format
|
||||
});
|
||||
cy.window()
|
||||
.its('frappe')
|
||||
.then(frappe => {
|
||||
// update sys_defaults value to avoid a reload
|
||||
frappe.sys_defaults.date_format = d.date_format;
|
||||
});
|
||||
|
||||
cy.new_form(doctype_name);
|
||||
cy.get('.form-control[data-fieldname=date]').focus();
|
||||
cy.get('.datepickers-container .datepicker.active')
|
||||
.should('be.visible');
|
||||
cy.get(
|
||||
'.datepickers-container .datepicker.active .datepicker--cell-day.-current-'
|
||||
).click();
|
||||
|
||||
cy.window()
|
||||
.its('cur_frm')
|
||||
.then(cur_frm => {
|
||||
let formatted_value = cur_frm.get_field('date').input.value;
|
||||
let parts = formatted_value.split(d.separator);
|
||||
expect(parts[d.part].length).to.equal(d.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Time formats', () => {
|
||||
let time_formats = [
|
||||
{
|
||||
time_format: 'HH:mm:ss',
|
||||
value: ' 11:00:12',
|
||||
match_value: '11:00:12'
|
||||
},
|
||||
{
|
||||
time_format: 'HH:mm',
|
||||
value: ' 11:00:12',
|
||||
match_value: '11:00'
|
||||
}
|
||||
];
|
||||
|
||||
time_formats.forEach(d => {
|
||||
it('test time format ' + d.time_format, () => {
|
||||
cy.set_value('System Settings', 'System Settings', {
|
||||
time_format: d.time_format
|
||||
});
|
||||
cy.window()
|
||||
.its('frappe')
|
||||
.then(frappe => {
|
||||
frappe.sys_defaults.time_format = d.time_format;
|
||||
});
|
||||
cy.new_form(doctype_name);
|
||||
cy.fill_field('time', d.value, 'Time').blur();
|
||||
cy.get_field('time').should('have.value', d.match_value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DateTime formats', () => {
|
||||
let datetime_formats = [
|
||||
{
|
||||
date_format: 'dd.mm.yyyy',
|
||||
time_format: 'HH:mm:ss',
|
||||
value: ' 02.12.2019 11:00:12',
|
||||
doc_value: '2019-12-02 11:00:12',
|
||||
input_value: '02.12.2019 11:00:12'
|
||||
},
|
||||
{
|
||||
date_format: 'mm-dd-yyyy',
|
||||
time_format: 'HH:mm',
|
||||
value: ' 12-02-2019 11:00:00',
|
||||
doc_value: '2019-12-02 11:00:00',
|
||||
input_value: '12-02-2019 11:00'
|
||||
}
|
||||
];
|
||||
datetime_formats.forEach(d => {
|
||||
it(`test datetime format ${d.date_format} ${d.time_format}`, () => {
|
||||
cy.set_value('System Settings', 'System Settings', {
|
||||
date_format: d.date_format,
|
||||
time_format: d.time_format
|
||||
});
|
||||
cy.window()
|
||||
.its('frappe')
|
||||
.then(frappe => {
|
||||
frappe.sys_defaults.date_format = d.date_format;
|
||||
frappe.sys_defaults.time_format = d.time_format;
|
||||
});
|
||||
cy.new_form(doctype_name);
|
||||
cy.fill_field('datetime', d.value, 'Datetime').blur();
|
||||
cy.get_field('datetime').should('have.value', d.input_value);
|
||||
|
||||
cy.window()
|
||||
.its('cur_frm.doc.datetime')
|
||||
.should('eq', d.doc_value);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -16,10 +16,12 @@ context('Form', () => {
|
|||
cy.get('.primary-action').click();
|
||||
cy.visit('/desk#List/ToDo');
|
||||
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||
cy.get('h1').should('be.visible').and('contain', 'To Do');
|
||||
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||
});
|
||||
it('navigates between documents with child table list filters applied', () => {
|
||||
cy.visit('/desk#List/Contact');
|
||||
cy.location('hash').should('eq', '#List/Contact/List');
|
||||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true });
|
||||
|
|
|
|||
|
|
@ -42,95 +42,156 @@ Cypress.Commands.add('login', (email, password) => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('call', (method, args) => {
|
||||
return cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
url: `/api/method/${method}`,
|
||||
method: 'POST',
|
||||
body: args,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
return cy
|
||||
.window()
|
||||
.its('frappe.csrf_token')
|
||||
.then(csrf_token => {
|
||||
return cy
|
||||
.request({
|
||||
url: `/api/method/${method}`,
|
||||
method: 'POST',
|
||||
body: args,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_list', (doctype, fields=[], filters=[]) => {
|
||||
return cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/resource/${doctype}?fields=${JSON.stringify(fields)}&filters=${JSON.stringify(filters)}`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
Cypress.Commands.add('get_list', (doctype, fields = [], filters = []) => {
|
||||
filters = JSON.stringify(filters);
|
||||
fields = JSON.stringify(fields);
|
||||
let url = `/api/resource/${doctype}?fields=${fields}&filters=${filters}`;
|
||||
return cy
|
||||
.window()
|
||||
.its('frappe.csrf_token')
|
||||
.then(csrf_token => {
|
||||
return cy
|
||||
.request({
|
||||
method: 'GET',
|
||||
url,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_doc', (doctype, name) => {
|
||||
return cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/resource/${doctype}/${name}`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
return cy
|
||||
.window()
|
||||
.its('frappe.csrf_token')
|
||||
.then(csrf_token => {
|
||||
return cy
|
||||
.request({
|
||||
method: 'GET',
|
||||
url: `/api/resource/${doctype}/${name}`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('create_doc', (doctype, args) => {
|
||||
return cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/resource/${doctype}`,
|
||||
body: args,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
|
||||
return cy
|
||||
.window()
|
||||
.its('frappe.csrf_token')
|
||||
.then(csrf_token => {
|
||||
return cy
|
||||
.request({
|
||||
method: 'POST',
|
||||
url: `/api/resource/${doctype}`,
|
||||
body: args,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
},
|
||||
failOnStatusCode: !ignore_duplicate
|
||||
})
|
||||
.then(res => {
|
||||
let status_codes = [200];
|
||||
if (ignore_duplicate) {
|
||||
status_codes.push(409);
|
||||
}
|
||||
expect(res.status).to.be.oneOf(status_codes);
|
||||
return res.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('remove_doc', (doctype, name) => {
|
||||
return cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/resource/${doctype}/${name}`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
}).then(res => {
|
||||
expect(res.status).eq(202);
|
||||
return res.body;
|
||||
return cy
|
||||
.window()
|
||||
.its('frappe.csrf_token')
|
||||
.then(csrf_token => {
|
||||
return cy
|
||||
.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/resource/${doctype}/${name}`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.status).eq(202);
|
||||
return res.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('create_records', (doc) => {
|
||||
return cy.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc })
|
||||
Cypress.Commands.add('create_records', doc => {
|
||||
return cy
|
||||
.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc })
|
||||
.then(r => r.message);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
|
||||
Cypress.Commands.add('set_value', (doctype, name, obj) => {
|
||||
return cy.call('frappe.client.set_value', {
|
||||
doctype,
|
||||
name,
|
||||
fieldname: obj
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
|
||||
cy.get_field(fieldname, fieldtype).as('input');
|
||||
|
||||
if (['Date', 'Time', 'Datetime'].includes(fieldtype)) {
|
||||
cy.get('@input').click().wait(200);
|
||||
cy.get('.datepickers-container .datepicker.active').should('exist');
|
||||
}
|
||||
if (fieldtype === 'Time') {
|
||||
cy.get('@input').clear();
|
||||
}
|
||||
|
||||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, { waitForAnimations: false });
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => {
|
||||
let selector = `.form-control[data-fieldname="${fieldname}"]`;
|
||||
|
||||
if (fieldtype === 'Text Editor') {
|
||||
|
|
@ -140,34 +201,33 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
|
|||
selector = `[data-fieldname="${fieldname}"] .ace_text-input`;
|
||||
}
|
||||
|
||||
cy.get(selector).as('input');
|
||||
|
||||
if (fieldtype === 'Select') {
|
||||
return cy.get('@input').select(value);
|
||||
} else {
|
||||
return cy.get('@input').type(value, {waitForAnimations: false});
|
||||
}
|
||||
return cy.get(selector);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('awesomebar', (text) => {
|
||||
Cypress.Commands.add('awesomebar', text => {
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 });
|
||||
});
|
||||
|
||||
Cypress.Commands.add('new_form', (doctype) => {
|
||||
cy.visit(`/desk#Form/${doctype}/New ${doctype} 1`);
|
||||
Cypress.Commands.add('new_form', doctype => {
|
||||
let route = `Form/${doctype}/New ${doctype} 1`;
|
||||
cy.visit(`/desk#${route}`);
|
||||
cy.get('body').should('have.attr', 'data-route', route);
|
||||
cy.get('body').should('have.attr', 'data-ajax-state', 'complete');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('go_to_list', (doctype) => {
|
||||
Cypress.Commands.add('go_to_list', doctype => {
|
||||
cy.visit(`/desk#List/${doctype}/List`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clear_cache', () => {
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.ui.toolbar.clear_cache();
|
||||
});
|
||||
cy.window()
|
||||
.its('frappe')
|
||||
.then(frappe => {
|
||||
frappe.ui.toolbar.clear_cache();
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('dialog', (opts) => {
|
||||
Cypress.Commands.add('dialog', opts => {
|
||||
return cy.window().then(win => {
|
||||
var d = new win.frappe.ui.Dialog(opts);
|
||||
d.show();
|
||||
|
|
@ -180,7 +240,9 @@ Cypress.Commands.add('get_open_dialog', () => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('hide_dialog', () => {
|
||||
cy.get_open_dialog().find('.btn-modal-close').click();
|
||||
cy.get_open_dialog()
|
||||
.find('.btn-modal-close')
|
||||
.click();
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"setup_complete",
|
||||
"date_and_number_format",
|
||||
"date_format",
|
||||
"time_format",
|
||||
"column_break_7",
|
||||
"number_format",
|
||||
"float_precision",
|
||||
|
|
@ -118,6 +119,14 @@
|
|||
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "HH:mm:ss",
|
||||
"fieldname": "time_format",
|
||||
"fieldtype": "Select",
|
||||
"label": "Time Format",
|
||||
"options": "HH:mm:ss\nHH:mm",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
|
|
@ -420,4 +429,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ def update_system_settings(args):
|
|||
"time_zone": args.get("timezone"),
|
||||
"float_precision": 3,
|
||||
'date_format': frappe.db.get_value("Country", args.get("country"), "date_format"),
|
||||
'time_format': frappe.db.get_value("Country", args.get("country"), "time_format"),
|
||||
'number_format': number_format,
|
||||
'enable_scheduler': 1 if not frappe.flags.in_test else 0,
|
||||
'backup_limit': 3 # Default for downloadable backups
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ from frappe.utils.momentjs import get_all_timezones
|
|||
def get_country_info(country=None):
|
||||
data = get_all()
|
||||
data = frappe._dict(data.get(country, {}))
|
||||
if not 'date_format' in data:
|
||||
if 'date_format' not in data:
|
||||
data.date_format = "dd-mm-yyyy"
|
||||
if 'time_format' not in data:
|
||||
data.time_format = "HH:mm:ss"
|
||||
|
||||
return data
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@
|
|||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "time_format",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Time format"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
|
|
@ -205,4 +211,4 @@
|
|||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,22 @@
|
|||
"website/js/bootstrap-4.js"
|
||||
],
|
||||
"js/control.min.js": [
|
||||
"node_modules/air-datepicker/dist/js/datepicker.min.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.cs.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.da.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.de.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.en.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.es.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.fi.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.fr.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.hu.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.nl.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.pl.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.pt-BR.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.pt.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.ro.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js",
|
||||
"node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js",
|
||||
"public/js/frappe/ui/capture.js",
|
||||
"public/js/frappe/form/controls/control.js"
|
||||
],
|
||||
|
|
@ -114,8 +130,6 @@
|
|||
"public/js/lib/socket.io.min.js",
|
||||
"public/js/lib/jSignature.min.js",
|
||||
"public/js/frappe/translate.js",
|
||||
"public/js/lib/datepicker/datepicker.min.js",
|
||||
"public/js/lib/datepicker/locale-all.js",
|
||||
"public/js/lib/leaflet/leaflet.js",
|
||||
"public/js/lib/leaflet/leaflet.draw.js",
|
||||
"public/js/lib/leaflet/L.Control.Locate.js",
|
||||
|
|
@ -314,9 +328,7 @@
|
|||
],
|
||||
"js/web_form.min.js": [
|
||||
"public/js/frappe/utils/datetime.js",
|
||||
"public/js/frappe/web_form/webform_script.js",
|
||||
"public/js/lib/datepicker/datepicker.min.js",
|
||||
"public/js/lib/datepicker/datepicker.en.js"
|
||||
"public/js/frappe/web_form/webform_script.js"
|
||||
],
|
||||
"css/web_form.css": [
|
||||
"public/less/list.less",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
|
||||
|
||||
frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({
|
||||
make_input: function() {
|
||||
this._super();
|
||||
this.make_picker();
|
||||
},
|
||||
make_picker: function() {
|
||||
this.set_date_options();
|
||||
this.set_datepicker();
|
||||
this.set_t_for_today();
|
||||
},
|
||||
set_formatted_input: function(value) {
|
||||
this._super(value);
|
||||
if (this.timepicker_only) return;
|
||||
if (!this.datepicker) return;
|
||||
if(!value) {
|
||||
this.datepicker.clear();
|
||||
|
|
@ -71,19 +74,6 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({
|
|||
}
|
||||
};
|
||||
},
|
||||
update_datepicker_position: function() {
|
||||
if(!this.frm) return;
|
||||
// show datepicker above or below the input
|
||||
// based on scroll position
|
||||
var window_height = $(window).height();
|
||||
var window_scroll_top = $(window).scrollTop();
|
||||
var el_offset_top = this.$input.offset().top + 280;
|
||||
var position = 'top left';
|
||||
if(window_height + window_scroll_top >= el_offset_top) {
|
||||
position = 'bottom left';
|
||||
}
|
||||
this.datepicker.update('position', position);
|
||||
},
|
||||
set_datepicker: function() {
|
||||
this.$input.datepicker(this.datepicker_options);
|
||||
this.datepicker = this.$input.data('datepicker');
|
||||
|
|
@ -96,6 +86,29 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({
|
|||
this.datepicker.selectDate(this.get_now_date());
|
||||
});
|
||||
},
|
||||
update_datepicker_position: function() {
|
||||
if(!this.frm) return;
|
||||
// show datepicker above or below the input
|
||||
// based on scroll position
|
||||
// We have to bodge around the timepicker getting its position
|
||||
// wrong by 42px when opening upwards.
|
||||
const $header = $('.page-head');
|
||||
const header_bottom = $header.position().top + $header.outerHeight();
|
||||
const picker_height = this.datepicker.$datepicker.outerHeight() + 12;
|
||||
const picker_top = this.$input.offset().top - $(window).scrollTop() - picker_height;
|
||||
|
||||
var position = 'top left';
|
||||
// 12 is the default datepicker.opts[offset]
|
||||
if (picker_top <= header_bottom) {
|
||||
position = 'bottom left';
|
||||
if (this.timepicker_only) this.datepicker.opts['offset'] = 12;
|
||||
} else {
|
||||
// To account for 42px incorrect positioning
|
||||
if (this.timepicker_only) this.datepicker.opts['offset'] = -30;
|
||||
}
|
||||
|
||||
this.datepicker.update('position', position);
|
||||
},
|
||||
get_now_date: function() {
|
||||
return frappe.datetime.now_date(true);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({
|
|||
set_date_options: function() {
|
||||
this._super();
|
||||
this.today_text = __("Now");
|
||||
let sysdefaults = frappe.boot.sysdefaults;
|
||||
this.date_format = frappe.defaultDatetimeFormat;
|
||||
let time_format = sysdefaults && sysdefaults.time_format
|
||||
? sysdefaults.time_format : 'HH:mm:ss';
|
||||
$.extend(this.datepicker_options, {
|
||||
timepicker: true,
|
||||
timeFormat: "hh:ii:ss"
|
||||
timeFormat: time_format.toLowerCase().replace("mm", "ii")
|
||||
});
|
||||
},
|
||||
get_now_date: function() {
|
||||
|
|
@ -22,5 +25,15 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({
|
|||
}
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
set_datepicker: function() {
|
||||
this._super();
|
||||
if (this.datepicker.opts.timeFormat.indexOf('s') == -1) {
|
||||
// No seconds in time format
|
||||
const $tp = this.datepicker.timepicker;
|
||||
$tp.$seconds.parent().css('display', 'none');
|
||||
$tp.$secondsText.css('display', 'none');
|
||||
$tp.$secondsText.prev().css('display', 'none');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,41 +1,70 @@
|
|||
frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({
|
||||
frappe.ui.form.ControlTime = frappe.ui.form.ControlDate.extend({
|
||||
set_formatted_input: function(value) {
|
||||
this._super(value);
|
||||
},
|
||||
make_input: function() {
|
||||
var me = this;
|
||||
this.timepicker_only = true;
|
||||
this._super();
|
||||
this.$input.datepicker({
|
||||
},
|
||||
make_picker: function() {
|
||||
this.set_time_options();
|
||||
this.set_datepicker();
|
||||
this.refresh();
|
||||
},
|
||||
set_time_options: function() {
|
||||
let sysdefaults = frappe.boot.sysdefaults;
|
||||
|
||||
let time_format = sysdefaults && sysdefaults.time_format
|
||||
? sysdefaults.time_format : 'HH:mm:ss';
|
||||
|
||||
this.time_format = frappe.defaultTimeFormat;
|
||||
this.datepicker_options = {
|
||||
language: "en",
|
||||
timepicker: true,
|
||||
onlyTimepicker: true,
|
||||
timeFormat: "hh:ii:ss",
|
||||
timeFormat: time_format.toLowerCase().replace("mm", "ii"),
|
||||
startDate: frappe.datetime.now_time(true),
|
||||
onSelect: function() {
|
||||
onSelect: () => {
|
||||
// ignore micro seconds
|
||||
if (moment(me.get_value(), 'hh:mm:ss').format('HH:mm:ss') != moment(me.value, 'hh:mm:ss').format('HH:mm:ss')) {
|
||||
me.$input.trigger('change');
|
||||
}
|
||||
if (moment(this.get_value(), time_format).format('HH:mm:ss') != moment(this.value, time_format).format('HH:mm:ss')) {
|
||||
this.$input.trigger('change');
|
||||
}
|
||||
},
|
||||
onShow: function() {
|
||||
onShow: () => {
|
||||
$('.datepicker--button:visible').text(__('Now'));
|
||||
|
||||
this.update_datepicker_position();
|
||||
},
|
||||
keyboardNav: false,
|
||||
todayButton: true
|
||||
});
|
||||
};
|
||||
},
|
||||
set_input: function(value) {
|
||||
this._super(value);
|
||||
if (value
|
||||
&& ((this.last_value && this.last_value !== this.value)
|
||||
|| (!this.datepicker.selectedDates.length))) {
|
||||
|
||||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, frappe.sys_defaults['time_format']));
|
||||
this.datepicker.selectDate(date_obj);
|
||||
}
|
||||
},
|
||||
set_datepicker: function() {
|
||||
this.$input.datepicker(this.datepicker_options);
|
||||
this.datepicker = this.$input.data('datepicker');
|
||||
|
||||
this.datepicker.$datepicker
|
||||
.find('[data-action="today"]')
|
||||
.click(() => {
|
||||
this.datepicker.selectDate(frappe.datetime.now_time(true));
|
||||
this.datepicker.hide();
|
||||
});
|
||||
this.refresh();
|
||||
},
|
||||
set_input: function(value) {
|
||||
this._super(value);
|
||||
if(value
|
||||
&& ((this.last_value && this.last_value !== this.value)
|
||||
|| (!this.datepicker.selectedDates.length))) {
|
||||
|
||||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, 'HH:mm:ss'));
|
||||
this.datepicker.selectDate(date_obj);
|
||||
if (this.datepicker.opts.timeFormat.indexOf('s') == -1) {
|
||||
// No seconds in time format
|
||||
const $tp = this.datepicker.timepicker;
|
||||
$tp.$seconds.parent().css('display', 'none');
|
||||
$tp.$secondsText.css('display', 'none');
|
||||
$tp.$secondsText.prev().css('display', 'none');
|
||||
}
|
||||
},
|
||||
set_description: function() {
|
||||
|
|
@ -49,5 +78,26 @@ frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({
|
|||
}
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
parse: function(value) {
|
||||
if (value) {
|
||||
return frappe.datetime.user_to_str(value, true);
|
||||
}
|
||||
},
|
||||
format_for_input: function(value) {
|
||||
if (value) {
|
||||
return frappe.datetime.str_to_user(value, true);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
validate: function(value) {
|
||||
if (value && !frappe.datetime.validate(value)) {
|
||||
let sysdefaults = frappe.sys_defaults;
|
||||
let time_format = sysdefaults && sysdefaults.time_format
|
||||
? sysdefaults.time_format : 'HH:mm:ss';
|
||||
frappe.msgprint(__("Time {0} must be in format: {1}", [value, time_format]));
|
||||
return '';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -156,7 +156,8 @@ frappe.form.formatters = {
|
|||
if(frappe.boot.sysdefaults.time_zone) {
|
||||
m = m.tz(frappe.boot.sysdefaults.time_zone);
|
||||
}
|
||||
return m.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ', h:mm a z');
|
||||
return m.format(frappe.boot.sysdefaults.date_format.toUpperCase()
|
||||
+ ' ' + frappe.boot.sysdefaults.time_format);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -180,6 +181,13 @@ frappe.form.formatters = {
|
|||
|
||||
return frappe.form.formatters.Data(value);
|
||||
},
|
||||
Time: function(value) {
|
||||
if (value) {
|
||||
value = frappe.datetime.str_to_user(value, true);
|
||||
}
|
||||
|
||||
return value || "";
|
||||
},
|
||||
LikedBy: function(value) {
|
||||
var html = "";
|
||||
$.each(JSON.parse(value || "[]"), function(i, v) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ $.extend(frappe.datetime, {
|
|||
},
|
||||
|
||||
obj_to_user: function(d) {
|
||||
return moment(d).format(frappe.datetime.get_user_fmt().toUpperCase());
|
||||
return moment(d).format(frappe.datetime.get_user_date_fmt().toUpperCase());
|
||||
},
|
||||
|
||||
get_diff: function(d1, d2) {
|
||||
|
|
@ -106,23 +106,32 @@ $.extend(frappe.datetime, {
|
|||
return moment().endOf("year").format();
|
||||
},
|
||||
|
||||
get_user_fmt: function() {
|
||||
get_user_time_fmt: function() {
|
||||
return frappe.sys_defaults && frappe.sys_defaults.time_format || "HH:mm:ss";
|
||||
},
|
||||
|
||||
get_user_date_fmt: function() {
|
||||
return frappe.sys_defaults && frappe.sys_defaults.date_format || "yyyy-mm-dd";
|
||||
},
|
||||
|
||||
get_user_fmt: function() { // For backwards compatibility only
|
||||
return frappe.sys_defaults && frappe.sys_defaults.date_format || "yyyy-mm-dd";
|
||||
},
|
||||
|
||||
str_to_user: function(val, only_time = false) {
|
||||
if(!val) return "";
|
||||
|
||||
var user_time_fmt = frappe.datetime.get_user_time_fmt();
|
||||
if(only_time) {
|
||||
return moment(val, frappe.defaultTimeFormat)
|
||||
.format(frappe.defaultTimeFormat);
|
||||
.format(user_time_fmt);
|
||||
}
|
||||
|
||||
var user_fmt = frappe.datetime.get_user_fmt().toUpperCase();
|
||||
var user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase();
|
||||
if(typeof val !== "string" || val.indexOf(" ")===-1) {
|
||||
return moment(val).format(user_fmt);
|
||||
return moment(val).format(user_date_fmt);
|
||||
} else {
|
||||
return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_fmt + " HH:mm:ss");
|
||||
return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_date_fmt + " " + user_time_fmt);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -132,16 +141,17 @@ $.extend(frappe.datetime, {
|
|||
|
||||
user_to_str: function(val, only_time = false) {
|
||||
|
||||
var user_time_fmt = frappe.datetime.get_user_time_fmt();
|
||||
if(only_time) {
|
||||
return moment(val, frappe.defaultTimeFormat)
|
||||
return moment(val, user_time_fmt)
|
||||
.format(frappe.defaultTimeFormat);
|
||||
}
|
||||
|
||||
var user_fmt = frappe.datetime.get_user_fmt().toUpperCase();
|
||||
var user_fmt = frappe.datetime.get_user_date_fmt().toUpperCase();
|
||||
var system_fmt = "YYYY-MM-DD";
|
||||
|
||||
if(val.indexOf(" ")!==-1) {
|
||||
user_fmt += " HH:mm:ss";
|
||||
user_fmt += " " + user_time_fmt;
|
||||
system_fmt += " HH:mm:ss";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
;(function ($) { $.fn.datepicker.language['en'] = {
|
||||
days: [__('Sunday'), __('Monday'), __('Tuesday'), __('Wednesday'), __('Thursday'), __('Friday'), __('Saturday')],
|
||||
daysShort: [__('Sun'), __('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat')],
|
||||
daysMin: [__('Su'), __('Mo'), __('Tu'), __('We'), __('Th'), __('Fr'), __('Sa')],
|
||||
months: [__('January'),__('February'),__('March'),__('April'),__('May'),__('June'), __('July'),__('August'),__('September'),__('October'),__('November'),__('December')],
|
||||
monthsShort: [__('Jan'), __('Feb'), __('Mar'), __('Apr'), __('May'), __('Jun'), __('Jul'), __('Aug'), __('Sep'), __('Oct'), __('Nov'), __('Dec')],
|
||||
today: __('Today'),
|
||||
clear: __('Clear'),
|
||||
dateFormat: 'mm/dd/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,263 +0,0 @@
|
|||
;(function ($) { $.fn.datepicker.language['ar'] = {
|
||||
days: ['الأحد', 'الأثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعه', 'السبت'],
|
||||
daysShort: ['الأحد', 'الأثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعه', 'السبت'],
|
||||
daysMin: ['الأحد', 'الأثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعه', 'السبت'],
|
||||
months: ['يناير','فبراير','مارس','أبريل','مايو','يونيو', 'يوليو','أغسطس','سبتمبر','اكتوبر','نوفمبر','ديسمبر'],
|
||||
monthsShort: ['يناير','فبراير','مارس','أبريل','مايو','يونيو', 'يوليو','أغسطس','سبتمبر','اكتوبر','نوفمبر','ديسمبر'],
|
||||
today: 'اليوم',
|
||||
clear: 'Clear',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['cs'] = {
|
||||
days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
|
||||
daysShort: ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'],
|
||||
daysMin: ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'],
|
||||
months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
|
||||
monthsShort: ['Led', 'Úno', 'Bře', 'Dub', 'Kvě', 'Čvn', 'Čvc', 'Srp', 'Zář', 'Říj', 'Lis', 'Pro'],
|
||||
today: 'Dnes',
|
||||
clear: 'Vymazat',
|
||||
dateFormat: 'dd.mm.yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['da'] = {
|
||||
days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
|
||||
daysShort: ['Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'],
|
||||
daysMin: ['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø'],
|
||||
months: ['Januar','Februar','Marts','April','Maj','Juni', 'Juli','August','September','Oktober','November','December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
today: 'I dag',
|
||||
clear: 'Nulstil',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery)
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['de'] = {
|
||||
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
|
||||
daysShort: ['Son', 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam'],
|
||||
daysMin: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
|
||||
months: ['Januar','Februar','März','April','Mai','Juni', 'Juli','August','September','Oktober','November','Dezember'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
|
||||
today: 'Heute',
|
||||
clear: 'Aufräumen',
|
||||
dateFormat: 'dd.mm.yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['en'] = {
|
||||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
||||
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
today: 'Today',
|
||||
clear: 'Clear',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['en-GB'] = {
|
||||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
||||
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
today: 'Today',
|
||||
clear: 'Clear',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['en-US'] = {
|
||||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
||||
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
today: 'Today',
|
||||
clear: 'Clear',
|
||||
dateFormat: 'mm/dd/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['es'] = {
|
||||
days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
|
||||
daysShort: ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab'],
|
||||
daysMin: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa'],
|
||||
months: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Augosto','Septiembre','Octubre','Noviembre','Diciembre'],
|
||||
monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
|
||||
today: 'Hoy',
|
||||
clear: 'Limpiar',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['fi'] = {
|
||||
days: ['Sunnuntai', 'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai'],
|
||||
daysShort: ['Su', 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La'],
|
||||
daysMin: ['Su', 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La'],
|
||||
months: ['Tammikuu','Helmikuu','Maaliskuu','Huhtikuu','Toukokuu','Kesäkuu', 'Heinäkuu','Elokuu','Syyskuu','Lokakuu','Marraskuu','Joulukuu'],
|
||||
monthsShort: ['Tammi', 'Helmi', 'Maalis', 'Huhti', 'Touko', 'Kesä', 'Heinä', 'Elo', 'Syys', 'Loka', 'Marras', 'Joulu'],
|
||||
today: 'Tänään',
|
||||
clear: 'Tyhjennä',
|
||||
dateFormat: 'dd.mm.yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['fr'] = {
|
||||
days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
|
||||
daysShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
|
||||
daysMin: ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'],
|
||||
months: ['Janvier','Février','Mars','Avril','Mai','Juin', 'Juillet','Août','Septembre','Octobre','Novembre','Decembre'],
|
||||
monthsShort: ['Jan', 'Fév', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
today: "Aujourd'hui",
|
||||
clear: 'Effacer',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['gr'] = {
|
||||
days: ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο'],
|
||||
daysShort: ['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ'],
|
||||
daysMin: ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πε', 'Πα', 'Σα'],
|
||||
months: ['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'],
|
||||
monthsShort: ['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μάι', 'Ι/ν', 'Ι/λ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
|
||||
today: 'Σήμερα',
|
||||
clear: 'Καθαρισμός',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { ;(function ($) { $.fn.datepicker.language['hu'] = {
|
||||
days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat'],
|
||||
daysShort: ['Va', 'Hé', 'Ke', 'Sze', 'Cs', 'Pé', 'Szo'],
|
||||
daysMin: ['V', 'H', 'K', 'Sz', 'Cs', 'P', 'Sz'],
|
||||
months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Már', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szep', 'Okt', 'Nov', 'Dec'],
|
||||
today: 'Ma',
|
||||
clear: 'Törlés',
|
||||
dateFormat: 'yyyy-mm-dd',
|
||||
timeFormat: 'hh:ii aa',
|
||||
firstDay: 1
|
||||
}; })(jQuery); })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['it'] = {
|
||||
days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
|
||||
daysShort: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
|
||||
daysMin: ['Do', 'Lu', 'Ma', 'Me', 'Gi', 'Ve', 'Sa'],
|
||||
months: ['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto',
|
||||
'Settembre','Ottobre','Novembre','Dicembre'],
|
||||
monthsShort: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
|
||||
today: 'Oggi',
|
||||
clear: 'Reset',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['nl'] = {
|
||||
days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
|
||||
daysShort: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
|
||||
daysMin: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
|
||||
months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
today: 'Vandaag',
|
||||
clear: 'Legen',
|
||||
dateFormat: 'dd-MM-yy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['pl'] = {
|
||||
days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
|
||||
daysShort: ['Nie', 'Pon', 'Wto', 'Śro', 'Czw', 'Pią', 'Sob'],
|
||||
daysMin: ['Nd', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'So'],
|
||||
months: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec', 'Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'],
|
||||
monthsShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
|
||||
today: 'Dzisiaj',
|
||||
clear: 'Wyczyść',
|
||||
dateFormat: 'yyyy-mm-dd',
|
||||
timeFormat: 'hh:ii:aa',
|
||||
firstDay: 1
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['pt-BR'] = {
|
||||
days: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'],
|
||||
daysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'],
|
||||
daysMin: ['Do', 'Se', 'Te', 'Qu', 'Qu', 'Se', 'Sa'],
|
||||
months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
|
||||
monthsShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
|
||||
today: 'Hoje',
|
||||
clear: 'Limpar',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 0
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['pt'] = {
|
||||
days: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'],
|
||||
daysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'],
|
||||
daysMin: ['Do', 'Se', 'Te', 'Qa', 'Qi', 'Sx', 'Sa'],
|
||||
months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
|
||||
monthsShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
|
||||
today: 'Hoje',
|
||||
clear: 'Limpar',
|
||||
dateFormat: 'dd/mm/yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['ro'] = {
|
||||
days: ['Duminică', 'Luni', 'Marţi', 'Miercuri', 'Joi', 'Vineri', 'Sâmbătă'],
|
||||
daysShort: ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'],
|
||||
daysMin: ['D', 'L', 'Ma', 'Mi', 'J', 'V', 'S'],
|
||||
months: ['Ianuarie','Februarie','Martie','Aprilie','Mai','Iunie','Iulie','August','Septembrie','Octombrie','Noiembrie','Decembrie'],
|
||||
monthsShort: ['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'],
|
||||
today: 'Azi',
|
||||
clear: 'Şterge',
|
||||
dateFormat: 'dd.mm.yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['sk'] = {
|
||||
days: ['Nedeľa', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota'],
|
||||
daysShort: ['Ned', 'Pon', 'Uto', 'Str', 'Štv', 'Pia', 'Sob'],
|
||||
daysMin: ['Ne', 'Po', 'Ut', 'St', 'Št', 'Pi', 'So'],
|
||||
months: ['Január','Február','Marec','Apríl','Máj','Jún', 'Júl','August','September','Október','November','December'],
|
||||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Máj', 'Jún', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
today: 'Dnes',
|
||||
clear: 'Vymazať',
|
||||
dateFormat: 'dd.mm.yyyy',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
||||
;(function ($) { $.fn.datepicker.language['zh'] = {
|
||||
days: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||
daysShort: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
daysMin: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
monthsShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
today: '今天',
|
||||
clear: '清除',
|
||||
dateFormat: 'yyyy-mm-dd',
|
||||
timeFormat: 'hh:ii',
|
||||
firstDay: 1
|
||||
}; })(jQuery);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
@import "variables.less";
|
||||
@import (less) "../js/lib/datepicker/datepicker.min.css";
|
||||
@import (less) "../../../node_modules/air-datepicker/dist/css/datepicker.min.css";
|
||||
|
||||
.datepicker {
|
||||
font-family: inherit;
|
||||
|
|
|
|||
163
frappe/tests/test_fmt_datetime.py
Normal file
163
frappe/tests/test_fmt_datetime.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
import frappe
|
||||
from frappe.utils import (
|
||||
getdate, get_datetime, get_time,
|
||||
get_user_date_format, get_user_time_format,
|
||||
formatdate, format_datetime, format_time)
|
||||
import unittest
|
||||
|
||||
test_date_obj = datetime.datetime.now()
|
||||
test_date = test_date_obj.strftime('%Y-%m-%d')
|
||||
test_time = test_date_obj.strftime('%H:%M:%S.%f')
|
||||
test_datetime = test_date_obj.strftime('%Y-%m-%d %H:%M:%S.%f')
|
||||
test_date_formats = {
|
||||
'yyyy-mm-dd': test_date_obj.strftime('%Y-%m-%d'),
|
||||
'dd-mm-yyyy': test_date_obj.strftime('%d-%m-%Y'),
|
||||
'dd/mm/yyyy': test_date_obj.strftime('%d/%m/%Y'),
|
||||
'dd.mm.yyyy': test_date_obj.strftime('%d.%m.%Y'),
|
||||
'mm/dd/yyyy': test_date_obj.strftime('%m/%d/%Y'),
|
||||
'mm-dd-yyyy': test_date_obj.strftime('%m-%d-%Y')}
|
||||
test_time_formats = {
|
||||
'HH:mm:ss': test_date_obj.strftime('%H:%M:%S'),
|
||||
'HH:mm': test_date_obj.strftime('%H:%M')}
|
||||
|
||||
|
||||
class TestFmtDatetime(unittest.TestCase):
|
||||
"""Tests date, time and datetime formatters and some associated
|
||||
utility functions. These rely on the system-wide date and time
|
||||
formats.
|
||||
"""
|
||||
|
||||
# Set up and tidy up routines
|
||||
|
||||
def setUp(self):
|
||||
# create test domain
|
||||
self.pre_test_date_format = frappe.db.get_default("date_format")
|
||||
self.pre_test_time_format = frappe.db.get_default("time_format")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.set_default("date_format", self.pre_test_date_format)
|
||||
frappe.db.set_default("time_format", self.pre_test_time_format)
|
||||
frappe.local.user_date_format = None
|
||||
frappe.local.user_time_format = None
|
||||
|
||||
# Test utility functions
|
||||
|
||||
def test_set_default_date_format(self):
|
||||
frappe.db.set_default("date_format", "ZYX321")
|
||||
self.assertEqual(frappe.db.get_default("date_format"), "ZYX321")
|
||||
|
||||
def test_set_default_time_format(self):
|
||||
frappe.db.set_default("time_format", "XYZ123")
|
||||
self.assertEqual(frappe.db.get_default("time_format"), "XYZ123")
|
||||
|
||||
def test_get_functions(self):
|
||||
# Test round-trip through getdate, get_datetime and get_time
|
||||
self.assertEqual(test_date_obj, get_datetime(test_datetime))
|
||||
self.assertEqual(test_date_obj.date(), getdate(test_date))
|
||||
self.assertEqual(test_date_obj.time(), get_time(test_time))
|
||||
|
||||
# Test date formatters
|
||||
|
||||
def test_formatdate_forced(self):
|
||||
# Test with forced date formats
|
||||
self.assertEqual(
|
||||
formatdate(test_date, 'dd-yyyy-mm'),
|
||||
test_date_obj.strftime('%d-%Y-%m'))
|
||||
self.assertEqual(
|
||||
formatdate(test_date, 'dd-yyyy-MM'),
|
||||
test_date_obj.strftime('%d-%Y-%m'))
|
||||
|
||||
def test_formatdate_forced_broken_locale(self):
|
||||
# Test with forced date formats
|
||||
lang = frappe.local.lang
|
||||
# Force fallback from Babel
|
||||
try:
|
||||
frappe.local.lang = 'FAKE'
|
||||
self.assertEqual(
|
||||
formatdate(test_date, 'dd-yyyy-mm'),
|
||||
test_date_obj.strftime('%d-%Y-%m'))
|
||||
self.assertEqual(
|
||||
formatdate(test_date, 'dd-yyyy-MM'),
|
||||
test_date_obj.strftime('%d-%Y-%m'))
|
||||
finally:
|
||||
frappe.local.lang = lang
|
||||
|
||||
def test_format_date(self):
|
||||
# Test formatdate with various default date formats set
|
||||
for fmt, valid_fmt in test_date_formats.items():
|
||||
frappe.db.set_default("date_format", fmt)
|
||||
frappe.local.user_date_format = None
|
||||
self.assertEqual(get_user_date_format(), fmt)
|
||||
self.assertEqual(formatdate(test_date), valid_fmt)
|
||||
|
||||
# Test time formatters
|
||||
|
||||
def test_format_time_forced(self):
|
||||
# Test with forced time formats
|
||||
self.assertEqual(
|
||||
format_time(test_time, 'ss:mm:HH'),
|
||||
test_date_obj.strftime('%S:%M:%H'))
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_format_time_forced_broken_locale(self):
|
||||
# Test with forced time formats
|
||||
# Currently format_time defaults to HH:mm:ss if the locale is
|
||||
# broken, so this is an expected failure.
|
||||
lang = frappe.local.lang
|
||||
try:
|
||||
# Force fallback from Babel
|
||||
frappe.local.lang = 'FAKE'
|
||||
self.assertEqual(
|
||||
format_time(test_time, 'ss:mm:HH'),
|
||||
test_date_obj.strftime('%S:%M:%H'))
|
||||
finally:
|
||||
frappe.local.lang = lang
|
||||
|
||||
def test_format_time(self):
|
||||
# Test format_time with various default time formats set
|
||||
for fmt, valid_fmt in test_time_formats.items():
|
||||
frappe.db.set_default("time_format", fmt)
|
||||
frappe.local.user_time_format = None
|
||||
self.assertEqual(get_user_time_format(), fmt)
|
||||
self.assertEqual(format_time(test_time), valid_fmt)
|
||||
|
||||
# Test datetime formatters
|
||||
|
||||
def test_format_datetime_forced(self):
|
||||
# Test with forced date formats
|
||||
self.assertEqual(
|
||||
format_datetime(test_datetime, 'dd-yyyy-MM ss:mm:HH'),
|
||||
test_date_obj.strftime('%d-%Y-%m %S:%M:%H'))
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_format_datetime_forced_broken_locale(self):
|
||||
# Test with forced datetime formats
|
||||
# Currently format_datetime defaults to yyyy-MM-dd HH:mm:ss
|
||||
# if the locale is broken, so this is an expected failure.
|
||||
lang = frappe.local.lang
|
||||
# Force fallback from Babel
|
||||
try:
|
||||
frappe.local.lang = 'FAKE'
|
||||
self.assertEqual(
|
||||
format_datetime(test_datetime, 'dd-yyyy-MM ss:mm:HH'),
|
||||
test_date_obj.strftime('%d-%Y-%m %S:%M:%H'))
|
||||
finally:
|
||||
frappe.local.lang = lang
|
||||
|
||||
def test_format_datetime(self):
|
||||
# Test formatdate with various default date formats set
|
||||
for date_fmt, valid_date in test_date_formats.items():
|
||||
frappe.db.set_default("date_format", date_fmt)
|
||||
frappe.local.user_date_format = None
|
||||
for time_fmt, valid_time in test_time_formats.items():
|
||||
frappe.db.set_default("time_format", time_fmt)
|
||||
frappe.local.user_time_format = None
|
||||
valid_fmt = valid_date + ' ' + valid_time
|
||||
self.assertEqual(
|
||||
format_datetime(test_datetime), valid_fmt)
|
||||
|
|
@ -209,22 +209,31 @@ def get_datetime_str(datetime_obj):
|
|||
datetime_obj = get_datetime(datetime_obj)
|
||||
return datetime_obj.strftime(DATETIME_FORMAT)
|
||||
|
||||
def get_user_format():
|
||||
if getattr(frappe.local, "user_format", None) is None:
|
||||
frappe.local.user_format = frappe.db.get_default("date_format")
|
||||
def get_user_date_format():
|
||||
"""Get the current user date format. The result will be cached."""
|
||||
if getattr(frappe.local, "user_date_format", None) is None:
|
||||
frappe.local.user_date_format = frappe.db.get_default("date_format")
|
||||
|
||||
return frappe.local.user_format or "yyyy-mm-dd"
|
||||
return frappe.local.user_date_format or "yyyy-mm-dd"
|
||||
|
||||
def formatdate(string_date=None, format_string=None):
|
||||
"""
|
||||
Converts the given string date to :data:`user_format`
|
||||
User format specified in defaults
|
||||
get_user_format = get_user_date_format # for backwards compatibility
|
||||
|
||||
Examples:
|
||||
def get_user_time_format():
|
||||
"""Get the current user time format. The result will be cached."""
|
||||
if getattr(frappe.local, "user_time_format", None) is None:
|
||||
frappe.local.user_time_format = frappe.db.get_default("time_format")
|
||||
|
||||
* dd-mm-yyyy
|
||||
* mm-dd-yyyy
|
||||
* dd/mm/yyyy
|
||||
return frappe.local.user_time_format or "HH:mm:ss"
|
||||
|
||||
def format_date(string_date=None, format_string=None):
|
||||
"""Converts the given string date to :data:`user_date_format`
|
||||
User format specified in defaults
|
||||
|
||||
Examples:
|
||||
|
||||
* dd-mm-yyyy
|
||||
* mm-dd-yyyy
|
||||
* dd/mm/yyyy
|
||||
"""
|
||||
|
||||
if not string_date:
|
||||
|
|
@ -232,29 +241,60 @@ def formatdate(string_date=None, format_string=None):
|
|||
|
||||
date = getdate(string_date)
|
||||
if not format_string:
|
||||
format_string = get_user_format()
|
||||
format_string = get_user_date_format()
|
||||
format_string = format_string.replace("mm", "MM")
|
||||
try:
|
||||
formatted_date = babel.dates.format_date(date, format_string, locale=(frappe.local.lang or "").replace("-", "_"))
|
||||
formatted_date = babel.dates.format_date(
|
||||
date, format_string,
|
||||
locale=(frappe.local.lang or "").replace("-", "_"))
|
||||
except UnknownLocaleError:
|
||||
format_string = format_string.replace("MM", "%m").replace("dd", "%d").replace("yyyy", "%Y")
|
||||
formatted_date = date.strftime(format_string)
|
||||
return formatted_date
|
||||
|
||||
def format_time(txt):
|
||||
formatdate = format_date # For backwards compatibility
|
||||
|
||||
def format_time(time_string=None, format_string=None):
|
||||
"""Converts the given string time to :data:`user_time_format`
|
||||
User format specified in defaults
|
||||
|
||||
Examples:
|
||||
|
||||
* HH:mm:ss
|
||||
* HH:mm
|
||||
"""
|
||||
|
||||
if not time_string:
|
||||
return ''
|
||||
|
||||
time_ = get_time(time_string)
|
||||
if not format_string:
|
||||
format_string = get_user_time_format()
|
||||
try:
|
||||
formatted_time = babel.dates.format_time(get_time(txt), locale=(frappe.local.lang or "").replace("-", "_"))
|
||||
formatted_time = babel.dates.format_time(
|
||||
time_, format_string,
|
||||
locale=(frappe.local.lang or "").replace("-", "_"))
|
||||
except UnknownLocaleError:
|
||||
formatted_time = get_time(txt).strftime("%H:%M:%S")
|
||||
formatted_time = time_.strftime("%H:%M:%S")
|
||||
return formatted_time
|
||||
|
||||
def format_datetime(datetime_string, format_string=None):
|
||||
"""Converts the given string time to :data:`user_datetime_format`
|
||||
User format specified in defaults
|
||||
|
||||
Examples:
|
||||
|
||||
* dd-mm-yyyy HH:mm:ss
|
||||
* mm-dd-yyyy HH:mm
|
||||
"""
|
||||
if not datetime_string:
|
||||
return
|
||||
|
||||
datetime = get_datetime(datetime_string)
|
||||
if not format_string:
|
||||
format_string = get_user_format().replace("mm", "MM") + " HH:mm:ss"
|
||||
format_string = (
|
||||
get_user_date_format().replace("mm", "MM")
|
||||
+ ' ' + get_user_time_format())
|
||||
|
||||
try:
|
||||
formatted_datetime = babel.dates.format_datetime(datetime, format_string, locale=(frappe.local.lang or "").replace("-", "_"))
|
||||
|
|
@ -363,14 +403,14 @@ def rounded(num, precision=0):
|
|||
# avoid rounding errors
|
||||
num = round(num * multiplier if precision else num, 8)
|
||||
|
||||
floor = math.floor(num)
|
||||
decimal_part = num - floor
|
||||
floor_num = math.floor(num)
|
||||
decimal_part = num - floor_num
|
||||
|
||||
if not precision and decimal_part == 0.5:
|
||||
num = floor if (floor % 2 == 0) else floor + 1
|
||||
num = floor_num if (floor_num % 2 == 0) else floor_num + 1
|
||||
else:
|
||||
if decimal_part == 0.5:
|
||||
num = floor + 1
|
||||
num = floor_num + 1
|
||||
else:
|
||||
num = round(num)
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ def add_country_and_currency(name, country):
|
|||
"country_name": name,
|
||||
"code": country.code,
|
||||
"date_format": country.date_format or "dd-mm-yyyy",
|
||||
"time_format": country.time_format or "HH:mm:ss",
|
||||
"time_zones": "\n".join(country.timezones or []),
|
||||
"docstatus": 0
|
||||
}).db_insert()
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ def get_safe_globals():
|
|||
datautils = frappe._dict()
|
||||
if frappe.db:
|
||||
date_format = frappe.db.get_default("date_format") or "yyyy-mm-dd"
|
||||
time_format = frappe.db.get_default("time_format") or "HH:mm:ss"
|
||||
else:
|
||||
date_format = 'yyyy-mm-dd'
|
||||
date_format = "yyyy-mm-dd"
|
||||
time_format = "HH:mm:ss"
|
||||
|
||||
add_module_properties(frappe.utils.data, datautils, lambda obj: hasattr(obj, "__call__"))
|
||||
|
||||
|
|
@ -44,54 +46,55 @@ def get_safe_globals():
|
|||
|
||||
out = frappe._dict(
|
||||
# make available limited methods of frappe
|
||||
json = json,
|
||||
dict = dict,
|
||||
frappe = frappe._dict(
|
||||
_ = frappe._,
|
||||
_dict = frappe._dict,
|
||||
flags = frappe.flags,
|
||||
json=json,
|
||||
dict=dict,
|
||||
frappe=frappe._dict(
|
||||
_=frappe._,
|
||||
_dict=frappe._dict,
|
||||
flags=frappe.flags,
|
||||
|
||||
format = frappe.format_value,
|
||||
format_value = frappe.format_value,
|
||||
date_format = date_format,
|
||||
format_date = frappe.utils.data.global_date_format,
|
||||
form_dict = getattr(frappe.local, 'form_dict', {}),
|
||||
format=frappe.format_value,
|
||||
format_value=frappe.format_value,
|
||||
date_format=date_format,
|
||||
time_format=time_format,
|
||||
format_date=frappe.utils.data.global_date_format,
|
||||
form_dict=getattr(frappe.local, 'form_dict', {}),
|
||||
|
||||
get_meta = frappe.get_meta,
|
||||
get_doc = frappe.get_doc,
|
||||
get_cached_doc = frappe.get_cached_doc,
|
||||
get_list = frappe.get_list,
|
||||
get_all = frappe.get_all,
|
||||
get_system_settings = frappe.get_system_settings,
|
||||
get_meta=frappe.get_meta,
|
||||
get_doc=frappe.get_doc,
|
||||
get_cached_doc=frappe.get_cached_doc,
|
||||
get_list=frappe.get_list,
|
||||
get_all=frappe.get_all,
|
||||
get_system_settings=frappe.get_system_settings,
|
||||
|
||||
utils = datautils,
|
||||
get_url = frappe.utils.get_url,
|
||||
render_template = frappe.render_template,
|
||||
msgprint = frappe.msgprint,
|
||||
utils=datautils,
|
||||
get_url=frappe.utils.get_url,
|
||||
render_template=frappe.render_template,
|
||||
msgprint=frappe.msgprint,
|
||||
|
||||
user = user,
|
||||
get_fullname = frappe.utils.get_fullname,
|
||||
get_gravatar = frappe.utils.get_gravatar_url,
|
||||
full_name = frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest",
|
||||
request = getattr(frappe.local, 'request', {}),
|
||||
session = frappe._dict(
|
||||
user = user,
|
||||
csrf_token = frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else ''
|
||||
user=user,
|
||||
get_fullname=frappe.utils.get_fullname,
|
||||
get_gravatar=frappe.utils.get_gravatar_url,
|
||||
full_name=frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest",
|
||||
request=getattr(frappe.local, 'request', {}),
|
||||
session=frappe._dict(
|
||||
user=user,
|
||||
csrf_token=frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else ''
|
||||
),
|
||||
socketio_port = frappe.conf.socketio_port,
|
||||
get_hooks = frappe.get_hooks,
|
||||
socketio_port=frappe.conf.socketio_port,
|
||||
get_hooks=frappe.get_hooks,
|
||||
),
|
||||
style = frappe._dict(
|
||||
border_color = '#d1d8dd'
|
||||
style=frappe._dict(
|
||||
border_color='#d1d8dd'
|
||||
),
|
||||
get_toc = get_toc,
|
||||
get_next_link = get_next_link,
|
||||
_ = frappe._,
|
||||
get_shade = get_shade,
|
||||
scrub = scrub,
|
||||
guess_mimetype = mimetypes.guess_type,
|
||||
html2text = html2text,
|
||||
dev_server = 1 if os.environ.get('DEV_SERVER', False) else 0
|
||||
get_toc=get_toc,
|
||||
get_next_link=get_next_link,
|
||||
_=frappe._,
|
||||
get_shade=get_shade,
|
||||
scrub=scrub,
|
||||
guess_mimetype=mimetypes.guess_type,
|
||||
html2text=html2text,
|
||||
dev_server=1 if os.environ.get('DEV_SERVER', False) else 0
|
||||
)
|
||||
|
||||
add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception))
|
||||
|
|
@ -99,6 +102,7 @@ def get_safe_globals():
|
|||
if not frappe.flags.in_setup_help:
|
||||
out.get_visible_columns = get_visible_columns
|
||||
out.frappe.date_format = date_format
|
||||
out.frappe.time_format = time_format
|
||||
out.frappe.db = frappe._dict(
|
||||
get_list = frappe.get_list,
|
||||
get_all = frappe.get_all,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"homepage": "https://frappe.io",
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.4.1",
|
||||
"air-datepicker": "http://github.com/frappe/air-datepicker",
|
||||
"awesomplete": "^1.1.2",
|
||||
"bootstrap": "^4.3.1",
|
||||
"cookie": "^0.3.1",
|
||||
|
|
|
|||
17
yarn.lock
17
yarn.lock
|
|
@ -210,6 +210,12 @@ after@0.8.2:
|
|||
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
|
||||
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
|
||||
|
||||
"air-datepicker@http://github.com/frappe/air-datepicker":
|
||||
version "2.2.3"
|
||||
resolved "http://github.com/frappe/air-datepicker#ed37b94d95c68d8544357e330be0c89d044a3eea"
|
||||
dependencies:
|
||||
jquery ">=2.0.0 <4.0.0"
|
||||
|
||||
ajv@^5.1.0:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||
|
|
@ -2560,6 +2566,11 @@ jpeg-js@^0.3.2:
|
|||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.6.tgz#c40382aac9506e7d1f2d856eb02f6c7b2a98b37c"
|
||||
integrity sha512-MUj2XlMB8kpe+8DJUGH/3UJm4XpI8XEgZQ+CiHDeyrGoKPdW/8FJv6ku+3UiYm5Fz3CWaL+iXmD8Q4Ap6aC1Jw==
|
||||
|
||||
"jquery@>=2.0.0 <4.0.0":
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
|
||||
integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
|
||||
|
||||
js-base64@^2.1.8, js-base64@^2.1.9:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
|
||||
|
|
@ -5356,9 +5367,9 @@ yargs-parser@^5.0.0:
|
|||
camelcase "^3.0.0"
|
||||
|
||||
yargs@^14.2:
|
||||
version "14.2.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.0.tgz#f116a9242c4ed8668790b40759b4906c276e76c3"
|
||||
integrity sha512-/is78VKbKs70bVZH7w4YaZea6xcJWOAwkhbR0CFuZBmYtfTYF0xjGJF43AYd8g2Uii1yJwmS5GR2vBmrc32sbg==
|
||||
version "14.2.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.2.tgz#2769564379009ff8597cdd38fba09da9b493c4b5"
|
||||
integrity sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==
|
||||
dependencies:
|
||||
cliui "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue